mirror of
https://github.com/tiennm99/serena.git
synced 2026-10-13 04:15:11 +00:00
Merge pull request #211 from oraios/solid-lsp
Add fully synchronous language server implementation (solidlsp)
This commit is contained in:
52 files changed
+11573
-212
No files matched your search
@@ -202,6 +202,7 @@ pylint.html
|
||||
/agent-ui
|
||||
# dynamic LS installations
|
||||
/src/multilspy/language_servers/*/static
|
||||
/src/solidlsp/language_servers/*/static
|
||||
|
||||
# Cache is in .multilspy
|
||||
**/.multilspy/**
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
# Solid-LSP: Deadlock-Free Language Server Architecture
|
||||
|
||||
## Overview
|
||||
|
||||
Solid-LSP (`src/solidlsp/`) is a **simplified, deadlock-resistant** reimplementation of the language server communication layer, designed to replace the problematic `multilspy` architecture that suffered from asyncio contamination issues when used with MCP servers.
|
||||
|
||||
## The Problem Solid-LSP Solves
|
||||
|
||||
### Root Cause: Asyncio Context Contamination
|
||||
The original `multilspy` implementation created **asyncio deadlocks** in MCP environments due to:
|
||||
|
||||
1. **Dual Event Loop Interference**: MCP server runs its own asyncio event loop, while SerenaAgent creates separate asyncio loops for language servers and dashboard
|
||||
2. **Coroutine Leakage**: When MCP server calls SerenaAgent tools, coroutines created by `multilspy.LanguageServer.request_full_symbol_tree()` were leaked into the MCP server's asyncio context but never awaited
|
||||
3. **Resource Exhaustion**: Accumulated unawaited coroutines in the MCP server's event loop eventually caused blocking and apparent "hangs"
|
||||
4. **MCP-Specific Issue**: Only occurred when using MCP clients, not in direct script execution
|
||||
|
||||
### Evidence from Logs
|
||||
```
|
||||
INFO mcp.server.lowlevel.server:_handle_message:524 - Warning: RuntimeWarning: coroutine 'LanguageServer.request_full_symbol_tree' was never awaited
|
||||
```
|
||||
|
||||
## Solid-LSP Architecture
|
||||
|
||||
### Core Design Principles
|
||||
|
||||
1. **Simplified Protocol Handling**: Eliminates the complex `lsp_protocol_handler` layer from `multilspy`
|
||||
2. **Direct Async Management**: More explicit control over async operations without complex threading abstractions
|
||||
3. **Single Process Operation**: Designed to work directly in the MCP server process without requiring process isolation
|
||||
4. **Clean Asyncio Boundaries**: Prevents coroutine leakage between async contexts
|
||||
|
||||
### Key Architectural Differences from multilspy
|
||||
|
||||
#### 1. Simplified Handler Architecture
|
||||
- **multilspy**: Complex `LanguageServerHandler` with extensive async task management and event loops
|
||||
- **solid-lsp**: Streamlined `SolidLanguageServerHandler` with direct process management
|
||||
|
||||
#### 2. Protocol Simplification
|
||||
- **No separate protocol handler package**: Direct LSP communication without abstraction layers
|
||||
- **Reduced complexity**: Fewer components and dependencies
|
||||
- **Same language server support**: Identical `language_servers/` implementations for all supported languages
|
||||
|
||||
#### 3. Enhanced Process Control
|
||||
**solid-lsp** provides additional methods for finer process control:
|
||||
- `_start_server_process()`: Direct process control
|
||||
- `_start_server()`: Server initialization
|
||||
- `_server_context` attribute: Enhanced server state management
|
||||
|
||||
## Process Isolation: No Longer Required
|
||||
|
||||
### Key Benefit: Single Process Operation
|
||||
|
||||
With solid-lsp, **process isolation is no longer needed** and is **disabled by default**:
|
||||
|
||||
```python
|
||||
# src/serena/mcp.py:581-585
|
||||
if USE_SOLID_LSP:
|
||||
mcp_factory = SerenaMCPFactorySingleProcess(context=context, project=project_file)
|
||||
else:
|
||||
# using multilspy requires process isolation to prevent asyncio contamination
|
||||
mcp_factory = SerenaMCPFactoryWithProcessIsolation(context=context, project=project_file)
|
||||
```
|
||||
|
||||
### Benefits of Single Process Operation
|
||||
1. **Lower Resource Usage**: No separate processes for SerenaAgent and language servers
|
||||
2. **Reduced Latency**: Direct method calls instead of inter-process communication
|
||||
3. **Simplified Architecture**: No IPC overhead or process management complexity
|
||||
4. **Better Performance**: Eliminated serialization/deserialization overhead
|
||||
5. **Easier Debugging**: All components in same process, simplified stack traces
|
||||
|
||||
### Why multilspy Required Process Isolation
|
||||
- **Asyncio Contamination**: multilspy's async patterns leaked coroutines into MCP server context
|
||||
- **Mandatory Isolation**: Process boundaries were the only way to prevent deadlocks
|
||||
- **Performance Penalty**: IPC overhead was necessary for stability
|
||||
|
||||
### How solid-lsp Enables Single Process
|
||||
- **Clean Async Patterns**: No coroutine leakage between MCP server and language server contexts
|
||||
- **Direct Integration**: Can safely run in same process as MCP server
|
||||
- **Stable Operation**: No more timeout issues or hanging operations
|
||||
|
||||
## Supported Language Servers
|
||||
|
||||
Solid-LSP maintains compatibility with all language servers from `multilspy`:
|
||||
|
||||
### Primary Languages
|
||||
- **Python**: Pyright Language Server (`src/solidlsp/language_servers/pyright_language_server/`)
|
||||
- **Java**: Eclipse JDTLS (`src/solidlsp/language_servers/eclipse_jdtls/`)
|
||||
- **TypeScript/JavaScript**: TypeScript Language Server (`src/solidlsp/language_servers/typescript_language_server/`)
|
||||
|
||||
### Additional Languages
|
||||
- **C#**: OmniSharp (`src/solidlsp/language_servers/omnisharp/`)
|
||||
- **Rust**: Rust-Analyzer (`src/solidlsp/language_servers/rust_analyzer/`)
|
||||
- **Go**: Gopls (`src/solidlsp/language_servers/gopls/`)
|
||||
- **Ruby**: Solargraph (`src/solidlsp/language_servers/solargraph/`)
|
||||
- **C++**: Clangd (`src/solidlsp/language_servers/clangd_language_server/`)
|
||||
- **Dart**: Dart Language Server (`src/solidlsp/language_servers/dart_language_server/`)
|
||||
- **PHP**: Intelephense (`src/solidlsp/language_servers/intelephense/`)
|
||||
- **Kotlin**: Kotlin Language Server (`src/solidlsp/language_servers/kotlin_language_server/`)
|
||||
|
||||
## Implementation Details
|
||||
|
||||
### Core Classes
|
||||
|
||||
#### `SolidLanguageServer` (`src/solidlsp/ls.py`)
|
||||
- **1,634 lines**: Main language server interface
|
||||
- **Enhanced methods**: Same semantic capabilities as `multilspy.LanguageServer` but with improved async handling
|
||||
- **Lifecycle control**: `start()`, `stop()`, `is_running()`, `language_server()` property (similar to multilspy's `SyncLanguageServer`)
|
||||
|
||||
#### `SolidLanguageServerHandler` (`src/solidlsp/ls_handler.py`)
|
||||
- **512 lines**: Simplified LSP protocol handler
|
||||
- **Direct process management**: More straightforward than `multilspy`'s complex async task orchestration
|
||||
- **Improved cleanup**: Better resource management and process termination
|
||||
|
||||
#### `SolidLspRequest` (`src/solidlsp/lsp_request.py`)
|
||||
- **Simplified request handling**: Direct LSP request/response without complex protocol abstractions
|
||||
|
||||
### Configuration Integration
|
||||
|
||||
Solid-LSP is controlled by the `USE_SOLID_LSP` constant in `src/serena/constants.py`:
|
||||
|
||||
```python
|
||||
USE_SOLID_LSP = True # Default: enabled
|
||||
```
|
||||
|
||||
This allows:
|
||||
- **Default Operation**: solid-lsp enabled by default for stability
|
||||
- **Fallback Capability**: Can switch back to multilspy + process isolation if needed
|
||||
- **A/B Testing**: Easy comparison between implementations
|
||||
|
||||
## Deadlock Prevention
|
||||
|
||||
### How Solid-LSP Prevents Deadlocks
|
||||
|
||||
1. **Simplified Async Patterns**: Reduces complexity that led to coroutine leakage
|
||||
2. **Clean Boundaries**: Proper async context management prevents MCP server contamination
|
||||
3. **Direct Operations**: Eliminates abstraction layers where async context could be corrupted
|
||||
4. **Single Process Safety**: Designed to work safely within MCP server process
|
||||
|
||||
### Verification
|
||||
|
||||
The solid-lsp approach provides:
|
||||
- **No more timeout issues**: Clean async boundaries prevent MCP server contamination
|
||||
- **Stable MCP operation**: Language server calls no longer hang after first timeout
|
||||
- **Resource efficiency**: Lower memory usage without separate processes
|
||||
- **Reliable semantic tools**: `find_symbol` and other tools work consistently
|
||||
- **Better Performance**: Direct method calls instead of IPC
|
||||
|
||||
## Migration Strategy
|
||||
|
||||
### Current Status
|
||||
- **Default**: Solid-LSP is the default implementation (`USE_SOLID_LSP = True`)
|
||||
- **Stable**: Resolves known deadlock issues in MCP environments
|
||||
- **Compatible**: Supports all existing language servers and semantic operations
|
||||
- **Performant**: Single process operation with lower overhead
|
||||
|
||||
### Rollback Path
|
||||
If issues arise, can fallback to multilspy with process isolation:
|
||||
1. **Disable solid-lsp**: Set `USE_SOLID_LSP = False`
|
||||
2. **Automatic fallback**: System automatically uses multilspy + process isolation
|
||||
3. **Performance trade-off**: Higher resource usage but guaranteed isolation
|
||||
|
||||
Solid-LSP represents an **architectural evolution** that eliminates the need for process isolation while maintaining all semantic capabilities and providing **better performance and stability** for MCP server deployments.
|
||||
@@ -2,6 +2,17 @@
|
||||
|
||||
Status of the `main` branch. Changes prior to the next official version change will appear here.
|
||||
|
||||
* **Reduce the use of asyncio to a minimum**, improving stability and reducing the need for workarounds
|
||||
* Switch to newly developed fully synchronous LSP library `solidlsp` (derived from `multilspy`)
|
||||
* Switch from fastapi (which uses asyncio) to Flask in the Serena dashboard
|
||||
* The MCP server is the only asynchronous component now, which resolves cross-component loop contamination,
|
||||
such that process isolation is no longer required.
|
||||
Neither are non-graceful shutdowns on Windows.
|
||||
|
||||
Fixes:
|
||||
* Fix `ExecuteShellCommandTool` and `GetCurrentConfigTool` hanging on Windows
|
||||
* Fix project activation by name via `--project` not working (was broken in previous release)
|
||||
|
||||
# 2025-06-20
|
||||
|
||||
* **Overhaul and major improvement of editing tools!**
|
||||
|
||||
+5
-5
@@ -23,7 +23,7 @@ dependencies = [
|
||||
"overrides>=7.7.0,<8",
|
||||
"python-dotenv>=1.0.0, <2",
|
||||
"mcp>=1.5.0",
|
||||
"fastapi>=0.115.12",
|
||||
"flask>=3.0.0",
|
||||
"sensai-utils>=1.4.0",
|
||||
"pydantic>=2.10.6",
|
||||
"types-pyyaml>=6.0.12.20241230",
|
||||
@@ -112,10 +112,10 @@ PYDEVD_DISABLE_FILE_VALIDATION = "1"
|
||||
# For custom markers, one can either adjust the env var or just use -m option in the command line,
|
||||
# as the second -m option will override the first one.
|
||||
test = "pytest test -vv -m \"${PYTEST_MARKERS:-not java and not rust and not isolated_process}\""
|
||||
_black_check = "black --check --exclude src/multilspy/ src scripts test"
|
||||
_ruff_check = "ruff check --exclude .venv/ --exclude src/multilspy/ src scripts test"
|
||||
_black_format = "black --exclude .venv/|src/multilspy/ src scripts test"
|
||||
_ruff_format = "ruff check --exclude src/multilspy/ --fix src scripts test"
|
||||
_black_check = "black --check --exclude src/multilspy/ --exclude src/solidlsp/ src scripts test"
|
||||
_ruff_check = "ruff check --exclude .venv/ --exclude src/multilspy/ --exclude src/solidlsp/ src scripts test"
|
||||
_black_format = "black --exclude .venv/|src/multilspy/|src/solidlsp/ src scripts test"
|
||||
_ruff_format = "ruff check --exclude src/multilspy/ --exclude src/solidlsp/ --fix src scripts test"
|
||||
lint = [
|
||||
"_black_check",
|
||||
"_ruff_check",
|
||||
|
||||
+11
-8
@@ -1,20 +1,23 @@
|
||||
__version__ = "2025-06-21"
|
||||
|
||||
import logging
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def serena_version() -> str:
|
||||
"""
|
||||
:return: the version of the package, including git status if available.
|
||||
"""
|
||||
from serena.util.git import get_git_status
|
||||
|
||||
version = __version__
|
||||
try:
|
||||
from sensai.util.git import git_status
|
||||
from sensai.util.logging import LoggingDisabledContext
|
||||
|
||||
with LoggingDisabledContext():
|
||||
git_status = git_status()
|
||||
version += f"-{git_status.commit[:8]}"
|
||||
if not git_status.is_clean:
|
||||
version += "-dirty"
|
||||
git_status = get_git_status()
|
||||
if git_status is not None:
|
||||
version += f"-{git_status.commit[:8]}"
|
||||
if not git_status.is_clean:
|
||||
version += "-dirty"
|
||||
except:
|
||||
pass
|
||||
return version
|
||||
+34
-14
@@ -39,7 +39,14 @@ from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_types import SymbolKind
|
||||
from serena import serena_version
|
||||
from serena.config import SerenaAgentContext, SerenaAgentMode
|
||||
from serena.constants import DEFAULT_ENCODING, PROJECT_TEMPLATE_FILE, REPO_ROOT, SELENA_CONFIG_TEMPLATE_FILE, SERENA_MANAGED_DIR_NAME
|
||||
from serena.constants import (
|
||||
DEFAULT_ENCODING,
|
||||
PROJECT_TEMPLATE_FILE,
|
||||
REPO_ROOT,
|
||||
SELENA_CONFIG_TEMPLATE_FILE,
|
||||
SERENA_MANAGED_DIR_NAME,
|
||||
USE_SOLID_LSP,
|
||||
)
|
||||
from serena.dashboard import MemoryLogHandler, SerenaDashboardAPI
|
||||
from serena.prompt_factory import PromptFactory, SerenaPromptFactory
|
||||
from serena.symbol import SymbolManager
|
||||
@@ -49,6 +56,7 @@ from serena.util.general import load_yaml, save_yaml
|
||||
from serena.util.inspection import determine_programming_language_composition, iter_subclasses
|
||||
from serena.util.shell import execute_shell_command
|
||||
from serena.util.thread import ExecutionResult, execute_with_timeout
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from serena.gui_log_viewer import GuiLogViewerHandler
|
||||
@@ -214,6 +222,10 @@ class ProjectConfig(ToStringMixin):
|
||||
return set(ToolRegistry.get_tool_class_by_name(tool_name) for tool_name in self.excluded_tools)
|
||||
|
||||
|
||||
class ProjectNotFoundError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass
|
||||
class Project:
|
||||
project_root: str
|
||||
@@ -666,12 +678,21 @@ def create_ls_for_project(
|
||||
)
|
||||
ls_logger = MultilspyLogger(log_level=log_level)
|
||||
log.info(f"Creating language server instance for {project_instance.project_root}.")
|
||||
return SyncLanguageServer.create(
|
||||
multilspy_config,
|
||||
ls_logger,
|
||||
project_instance.project_root,
|
||||
timeout=ls_timeout,
|
||||
)
|
||||
if USE_SOLID_LSP:
|
||||
ls = SolidLanguageServer.create(
|
||||
multilspy_config,
|
||||
ls_logger,
|
||||
project_instance.project_root,
|
||||
timeout=ls_timeout,
|
||||
)
|
||||
return cast(SyncLanguageServer, ls) # TODO: Fix type
|
||||
else:
|
||||
return SyncLanguageServer.create(
|
||||
multilspy_config,
|
||||
ls_logger,
|
||||
project_instance.project_root,
|
||||
timeout=ls_timeout,
|
||||
)
|
||||
|
||||
|
||||
@click.command()
|
||||
@@ -715,11 +736,10 @@ class SerenaAgent:
|
||||
:param modes: list of modes in which the agent is operating (they will be combined), None for default modes.
|
||||
The modes may adjust prompts, tool availability, and tool descriptions.
|
||||
:param serena_config: the Serena configuration or None to read the configuration from the default location.
|
||||
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
|
||||
:param enable_gui_log_window: Whether to enable the GUI log window. It currently does not work on macOS, and setting this to True will be ignored then.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param log_level: Log level for the GUI log window. If not specified, will take the value from the serena configuration.
|
||||
:param tool_timeout: Timeout in seconds for tool execution. If not specified, will take the value from the serena configuration.
|
||||
:param enable_web_dashboard: whether to enable the web dashboard; If None, will take the value from the Serena configuration.
|
||||
:param enable_gui_log_window: whether to enable the GUI log window; If None, will take the value from the Serena configuration.
|
||||
:param log_level: the log level for the GUI log window; If None, will take the value from the serena configuration.
|
||||
:param tool_timeout: the timeout in seconds for tool execution. If None, will take the value from the serena configuration.
|
||||
"""
|
||||
# obtain serena configuration using the decoupled factory function
|
||||
self.serena_config = create_serena_config(
|
||||
@@ -802,7 +822,7 @@ class SerenaAgent:
|
||||
if project is not None:
|
||||
try:
|
||||
self.activate_project_from_path_or_name(project)
|
||||
except Exception as e:
|
||||
except ProjectNotFoundError as e:
|
||||
log.error(
|
||||
f"Error activating project '{project}': {e}; Note that out-of-project configurations were migrated. "
|
||||
"You should now pass either --project <project_name> or --project <project_root>."
|
||||
@@ -969,7 +989,7 @@ class SerenaAgent:
|
||||
log.info(f"Found registered project {project_instance.project_name} at path {project_instance.project_root}.")
|
||||
else:
|
||||
if not os.path.isdir(project_root_or_name):
|
||||
raise ValueError(
|
||||
raise ProjectNotFoundError(
|
||||
f"Project '{project_root_or_name}' not found: Not a valid project name or directory. "
|
||||
f"Existing project names: {self.serena_config.project_names}"
|
||||
)
|
||||
|
||||
@@ -18,3 +18,9 @@ DEFAULT_MODES = ("interactive", "editing")
|
||||
|
||||
PROJECT_TEMPLATE_FILE = str(_serena_pkg_path / "resources" / "project.template.yml")
|
||||
SELENA_CONFIG_TEMPLATE_FILE = str(_serena_pkg_path / "resources" / "serena_config.template.yml")
|
||||
|
||||
USE_SOLID_LSP = True
|
||||
"""Whether to use Solid LSP instead of multilspy for language server operations.
|
||||
Multilspy uses asyncio, which creates many problems like deadlocks and requires full process
|
||||
isolation (and even then some issues remain).
|
||||
"""
|
||||
+45
-13
@@ -3,10 +3,9 @@ import queue
|
||||
import socket
|
||||
import threading
|
||||
from collections.abc import Callable
|
||||
from typing import Any
|
||||
|
||||
import uvicorn
|
||||
from fastapi import FastAPI
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
from flask import Flask, Response, request, send_from_directory
|
||||
from pydantic import BaseModel
|
||||
from sensai.util import logging
|
||||
|
||||
@@ -14,6 +13,9 @@ from serena.constants import SERENA_DASHBOARD_DIR
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
# disable Werkzeug's logging to avoid cluttering the output
|
||||
logging.getLogger("werkzeug").setLevel(logging.WARNING)
|
||||
|
||||
|
||||
class MemoryLogHandler(logging.Handler):
|
||||
def __init__(self, level: int = logging.NOTSET) -> None:
|
||||
@@ -74,7 +76,7 @@ class SerenaDashboardAPI:
|
||||
self._memory_log_handler = memory_log_handler
|
||||
self._tool_names = tool_names
|
||||
self._shutdown_callback = shutdown_callback
|
||||
self._app = FastAPI(title="Serena Dashboard")
|
||||
self._app = Flask(__name__)
|
||||
self._setup_routes()
|
||||
|
||||
@property
|
||||
@@ -82,21 +84,46 @@ class SerenaDashboardAPI:
|
||||
return self._memory_log_handler
|
||||
|
||||
def _setup_routes(self) -> None:
|
||||
self._app.mount("/dashboard", StaticFiles(directory=SERENA_DASHBOARD_DIR), name="dashboard")
|
||||
# Static files
|
||||
@self._app.route("/dashboard/<path:filename>")
|
||||
def serve_dashboard(filename: str) -> Response:
|
||||
return send_from_directory(SERENA_DASHBOARD_DIR, filename)
|
||||
|
||||
self._app.add_api_route("/get_log_messages", self._get_log_messages, methods=["POST"], response_model=ResponseLog)
|
||||
self._app.add_api_route("/get_tool_names", self._get_tool_names, methods=["GET"], response_model=ResponseToolNames)
|
||||
self._app.add_api_route("/shutdown", self._shutdown, methods=["PUT"])
|
||||
@self._app.route("/dashboard/")
|
||||
def serve_dashboard_index() -> Response:
|
||||
return send_from_directory(SERENA_DASHBOARD_DIR, "index.html")
|
||||
|
||||
async def _get_log_messages(self, request: RequestLog) -> ResponseLog:
|
||||
# API routes
|
||||
@self._app.route("/get_log_messages", methods=["POST"])
|
||||
def get_log_messages() -> dict[str, Any]:
|
||||
request_data = request.get_json()
|
||||
if not request_data:
|
||||
request_log = RequestLog()
|
||||
else:
|
||||
request_log = RequestLog.model_validate(request_data)
|
||||
|
||||
result = self._get_log_messages(request_log)
|
||||
return result.model_dump()
|
||||
|
||||
@self._app.route("/get_tool_names", methods=["GET"])
|
||||
def get_tool_names() -> dict[str, Any]:
|
||||
result = self._get_tool_names()
|
||||
return result.model_dump()
|
||||
|
||||
@self._app.route("/shutdown", methods=["PUT"])
|
||||
def shutdown() -> dict[str, str]:
|
||||
self._shutdown()
|
||||
return {"status": "shutting down"}
|
||||
|
||||
def _get_log_messages(self, request_log: RequestLog) -> ResponseLog:
|
||||
all_messages = self._memory_log_handler.get_log_messages()
|
||||
requested_messages = all_messages[request.start_idx :] if request.start_idx <= len(all_messages) else []
|
||||
requested_messages = all_messages[request_log.start_idx :] if request_log.start_idx <= len(all_messages) else []
|
||||
return ResponseLog(messages=requested_messages, max_idx=len(all_messages) - 1)
|
||||
|
||||
async def _get_tool_names(self) -> ResponseToolNames:
|
||||
def _get_tool_names(self) -> ResponseToolNames:
|
||||
return ResponseToolNames(tool_names=self._tool_names)
|
||||
|
||||
async def _shutdown(self) -> None:
|
||||
def _shutdown(self) -> None:
|
||||
log.info("Shutting down Serena")
|
||||
if self._shutdown_callback:
|
||||
self._shutdown_callback()
|
||||
@@ -125,7 +152,12 @@ class SerenaDashboardAPI:
|
||||
"""
|
||||
Runs the dashboard on the given host and port and returns the port number.
|
||||
"""
|
||||
uvicorn.run(self._app, host=host, port=port, workers=1, log_config=None, log_level="critical")
|
||||
# patch flask.cli.show_server to avoid printing the server info
|
||||
from flask import cli
|
||||
|
||||
cli.show_server_banner = lambda *args, **kwargs: None
|
||||
|
||||
self._app.run(host=host, port=port, debug=False, use_reloader=False, threaded=True)
|
||||
return port
|
||||
|
||||
def run_in_thread(self) -> tuple[threading.Thread, int]:
|
||||
|
||||
+316
-142
@@ -9,7 +9,8 @@ import signal
|
||||
import sys
|
||||
import threading
|
||||
import time
|
||||
from collections.abc import AsyncIterator, Sequence
|
||||
from abc import abstractmethod
|
||||
from collections.abc import AsyncIterator, Iterator, Sequence
|
||||
from contextlib import asynccontextmanager
|
||||
from dataclasses import dataclass
|
||||
from logging import Formatter, Logger, StreamHandler
|
||||
@@ -23,19 +24,19 @@ from mcp.server.fastmcp.server import FastMCP, Settings
|
||||
from mcp.server.fastmcp.tools.base import Tool as MCPTool
|
||||
from pydantic_settings import SettingsConfigDict
|
||||
from sensai.util import logging
|
||||
from sensai.util.helper import mark_used
|
||||
|
||||
from serena.agent import (
|
||||
ActivateProjectTool,
|
||||
Project,
|
||||
SerenaAgent,
|
||||
SerenaConfig,
|
||||
ToolInterface,
|
||||
ToolRegistry,
|
||||
create_serena_config,
|
||||
show_fatal_exception_safe,
|
||||
)
|
||||
from serena.config import RegisteredContext, SerenaAgentContext, SerenaAgentMode
|
||||
from serena.constants import DEFAULT_CONTEXT, DEFAULT_MODES
|
||||
from serena.constants import DEFAULT_CONTEXT, DEFAULT_MODES, USE_SOLID_LSP
|
||||
from serena.process_isolated_agent import (
|
||||
ProcessIsolatedDashboard,
|
||||
ProcessIsolatedSerenaAgent,
|
||||
@@ -66,152 +67,324 @@ class SerenaMCPRequestContext:
|
||||
agent: SerenaAgent
|
||||
|
||||
|
||||
def make_tool(
|
||||
tool: ToolInterface,
|
||||
) -> MCPTool:
|
||||
func_name = tool.get_name()
|
||||
func_doc = tool.get_apply_docstring() or ""
|
||||
func_arg_metadata = tool.get_apply_fn_metadata()
|
||||
is_async = False
|
||||
parameters = func_arg_metadata.arg_model.model_json_schema()
|
||||
class SerenaMCPFactory:
|
||||
def __init__(self, context: str = DEFAULT_CONTEXT, project: str | None = None):
|
||||
"""
|
||||
:param context: The context name or path to context file
|
||||
:param project: Either an absolute path to the project directory or a name of an already registered project.
|
||||
If the project passed here hasn't been registered yet, it will be registered automatically and can be activated by its name
|
||||
afterward.
|
||||
"""
|
||||
self.context = SerenaAgentContext.load(context)
|
||||
self.project = project
|
||||
|
||||
docstring = docstring_parser.parse(func_doc)
|
||||
@staticmethod
|
||||
def make_mcp_tool(tool: ToolInterface) -> MCPTool:
|
||||
func_name = tool.get_name()
|
||||
func_doc = tool.get_apply_docstring() or ""
|
||||
func_arg_metadata = tool.get_apply_fn_metadata()
|
||||
is_async = False
|
||||
parameters = func_arg_metadata.arg_model.model_json_schema()
|
||||
|
||||
# Mount the tool description as a combination of the docstring description and
|
||||
# the return value description, if it exists.
|
||||
if docstring.description:
|
||||
func_doc = f"{docstring.description.strip().strip('.')}."
|
||||
else:
|
||||
func_doc = ""
|
||||
if docstring.returns and (docstring_returns_descr := docstring.returns.description):
|
||||
# Only add a space before "Returns" if func_doc is not empty
|
||||
prefix = " " if func_doc else ""
|
||||
func_doc = f"{func_doc}{prefix}Returns {docstring_returns_descr.strip().strip('.')}."
|
||||
docstring = docstring_parser.parse(func_doc)
|
||||
|
||||
# Parse the parameter descriptions from the docstring and add pass its description
|
||||
# to the parameters schema.
|
||||
docstring_params = {param.arg_name: param for param in docstring.params}
|
||||
parameters_properties: dict[str, dict[str, Any]] = parameters["properties"]
|
||||
for parameter, properties in parameters_properties.items():
|
||||
if (param_doc := docstring_params.get(parameter)) and param_doc.description:
|
||||
param_desc = f"{param_doc.description.strip().strip('.') + '.'}"
|
||||
properties["description"] = param_desc[0].upper() + param_desc[1:]
|
||||
# Mount the tool description as a combination of the docstring description and
|
||||
# the return value description, if it exists.
|
||||
if docstring.description:
|
||||
func_doc = f"{docstring.description.strip().strip('.')}."
|
||||
else:
|
||||
func_doc = ""
|
||||
if docstring.returns and (docstring_returns_descr := docstring.returns.description):
|
||||
# Only add a space before "Returns" if func_doc is not empty
|
||||
prefix = " " if func_doc else ""
|
||||
func_doc = f"{func_doc}{prefix}Returns {docstring_returns_descr.strip().strip('.')}."
|
||||
|
||||
def execute_fn(**kwargs) -> str: # type: ignore
|
||||
return tool.apply_ex(log_call=True, catch_exceptions=True, **kwargs)
|
||||
# Parse the parameter descriptions from the docstring and add pass its description
|
||||
# to the parameter schema.
|
||||
docstring_params = {param.arg_name: param for param in docstring.params}
|
||||
parameters_properties: dict[str, dict[str, Any]] = parameters["properties"]
|
||||
for parameter, properties in parameters_properties.items():
|
||||
if (param_doc := docstring_params.get(parameter)) and param_doc.description:
|
||||
param_desc = f"{param_doc.description.strip().strip('.') + '.'}"
|
||||
properties["description"] = param_desc[0].upper() + param_desc[1:]
|
||||
|
||||
return MCPTool(
|
||||
fn=execute_fn,
|
||||
name=func_name,
|
||||
description=func_doc,
|
||||
parameters=parameters,
|
||||
fn_metadata=func_arg_metadata,
|
||||
is_async=is_async,
|
||||
context_kwarg=None,
|
||||
annotations=None,
|
||||
)
|
||||
def execute_fn(**kwargs) -> str: # type: ignore
|
||||
return tool.apply_ex(log_call=True, catch_exceptions=True, **kwargs)
|
||||
|
||||
return MCPTool(
|
||||
fn=execute_fn,
|
||||
name=func_name,
|
||||
description=func_doc,
|
||||
parameters=parameters,
|
||||
fn_metadata=func_arg_metadata,
|
||||
is_async=is_async,
|
||||
context_kwarg=None,
|
||||
annotations=None,
|
||||
)
|
||||
|
||||
def create_mcp_server_and_agent(
|
||||
project: str | None,
|
||||
host: str = "0.0.0.0",
|
||||
port: int = 8000,
|
||||
context: str = DEFAULT_CONTEXT,
|
||||
modes: Sequence[str] = DEFAULT_MODES,
|
||||
enable_web_dashboard: bool | None = None,
|
||||
enable_gui_log_window: bool | None = None,
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
|
||||
trace_lsp_communication: bool | None = None,
|
||||
tool_timeout: float | None = None,
|
||||
) -> tuple[FastMCP, ProcessIsolatedSerenaAgent]:
|
||||
"""
|
||||
Create an MCP server with process-isolated SerenaAgent to prevent asyncio contamination.
|
||||
@abstractmethod
|
||||
def _iter_tools(self) -> Iterator[ToolInterface]:
|
||||
pass
|
||||
|
||||
:param project: "Either an absolute path to the project directory or a name of an already registered project. "
|
||||
"If the project passed here hasn't been registered yet, it will be registered automatically and can be activated by its name "
|
||||
"afterwards.
|
||||
:param host: The host to bind to
|
||||
:param port: The port to bind to
|
||||
:param context: The context name or path to context file
|
||||
:param modes: List of mode names or paths to mode files
|
||||
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
|
||||
:param enable_gui_log_window: Whether to enable the GUI log window. It currently does not work on macOS, and setting this to True will be ignored then.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param log_level: Log level. If not specified, will take the value from the serena configuration.
|
||||
:param trace_lsp_communication: Whether to trace the communication between Serena and the language servers.
|
||||
This is useful for debugging language server issues.
|
||||
:param tool_timeout: Timeout in seconds for tool execution. If not specified, will take the value from the serena configuration.
|
||||
"""
|
||||
mcp: FastMCP | None = None
|
||||
context_instance = SerenaAgentContext.load(context)
|
||||
modes_instances = [SerenaAgentMode.load(mode) for mode in modes]
|
||||
# noinspection PyProtectedMember
|
||||
def _set_mcp_tools(self, mcp: FastMCP) -> None:
|
||||
"""Update the tools in the MCP server"""
|
||||
if mcp is not None:
|
||||
mcp._tool_manager._tools = {}
|
||||
for tool in self._iter_tools():
|
||||
mcp_tool = self.make_mcp_tool(tool)
|
||||
mcp._tool_manager._tools[tool.get_name()] = mcp_tool
|
||||
|
||||
is_ide_assistant = context_instance.name == RegisteredContext.IDE_ASSISTANT.value
|
||||
@abstractmethod
|
||||
def _instantiate_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> None:
|
||||
pass
|
||||
|
||||
project_instance: Project | None = None
|
||||
if project is not None:
|
||||
# Fail early if the project cannot be loaded
|
||||
log.info(f"Will activate project {project} at mcp server startup")
|
||||
def create_mcp_server(
|
||||
self,
|
||||
host: str = "0.0.0.0",
|
||||
port: int = 8000,
|
||||
modes: Sequence[str] = DEFAULT_MODES,
|
||||
enable_web_dashboard: bool | None = None,
|
||||
enable_gui_log_window: bool | None = None,
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
|
||||
trace_lsp_communication: bool | None = None,
|
||||
tool_timeout: float | None = None,
|
||||
) -> FastMCP:
|
||||
"""
|
||||
Create an MCP server with process-isolated SerenaAgent to prevent asyncio contamination.
|
||||
|
||||
:param host: The host to bind to
|
||||
:param port: The port to bind to
|
||||
:param modes: List of mode names or paths to mode files
|
||||
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
|
||||
:param enable_gui_log_window: Whether to enable the GUI log window. It currently does not work on macOS, and setting this to True will be ignored then.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param log_level: Log level. If not specified, will take the value from the serena configuration.
|
||||
:param trace_lsp_communication: Whether to trace the communication between Serena and the language servers.
|
||||
This is useful for debugging language server issues.
|
||||
:param tool_timeout: Timeout in seconds for tool execution. If not specified, will take the value from the serena configuration.
|
||||
"""
|
||||
try:
|
||||
project_instance = Project.load(project)
|
||||
serena_config = create_serena_config(
|
||||
enable_web_dashboard=enable_web_dashboard,
|
||||
enable_gui_log_window=enable_gui_log_window,
|
||||
log_level=log_level,
|
||||
trace_lsp_communication=trace_lsp_communication,
|
||||
tool_timeout=tool_timeout,
|
||||
)
|
||||
modes_instances = [SerenaAgentMode.load(mode) for mode in modes]
|
||||
self._instantiate_agent(serena_config, modes_instances)
|
||||
|
||||
except Exception as e:
|
||||
log.error(f"Failed to load or generate project config for {project}: {e}")
|
||||
show_fatal_exception_safe(e)
|
||||
raise
|
||||
else:
|
||||
log.warning("No project passed at startup, you will have to activate a project yourself (by asking the agent to do so).")
|
||||
|
||||
# Override model_config to disable the use of `.env` files for reading settings, because user projects are likely to contain
|
||||
# `.env` files (e.g. containing LOG_LEVEL) that are not supposed to override the MCP settings;
|
||||
# retain only FASTMCP_ prefix for already set environment variables.
|
||||
Settings.model_config = SettingsConfigDict(env_prefix="FASTMCP_")
|
||||
|
||||
mcp_settings: Settings = Settings(lifespan=self.server_lifespan, host=host, port=port)
|
||||
mcp = FastMCP(**mcp_settings.model_dump())
|
||||
return mcp
|
||||
|
||||
@asynccontextmanager
|
||||
@abstractmethod
|
||||
async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]:
|
||||
"""Manage server startup and shutdown lifecycle."""
|
||||
yield None # ensures MyPy understands we yield None
|
||||
|
||||
|
||||
class SerenaMCPFactorySingleProcess(SerenaMCPFactory):
|
||||
"""
|
||||
MCP server factory where the SerenaAgent and its language server run in the same process as the MCP server
|
||||
"""
|
||||
|
||||
def __init__(self, context: str = DEFAULT_CONTEXT, project: str | None = None):
|
||||
"""
|
||||
:param context: The context name or path to context file
|
||||
:param project: Either an absolute path to the project directory or a name of an already registered project.
|
||||
If the project passed here hasn't been registered yet, it will be registered automatically and can be activated by its name
|
||||
afterward.
|
||||
"""
|
||||
super().__init__(context=context, project=project)
|
||||
self.agent: SerenaAgent | None = None
|
||||
|
||||
def _instantiate_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> None:
|
||||
self.agent = SerenaAgent(project=self.project, serena_config=serena_config, context=self.context, modes=modes)
|
||||
|
||||
def _iter_tools(self) -> Iterator[ToolInterface]:
|
||||
assert self.agent is not None
|
||||
yield from self.agent.get_exposed_tool_instances()
|
||||
|
||||
@asynccontextmanager
|
||||
async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]:
|
||||
self._set_mcp_tools(mcp_server)
|
||||
yield
|
||||
|
||||
|
||||
class SerenaMCPFactoryWithProcessIsolation(SerenaMCPFactory):
|
||||
"""
|
||||
MCP server factory with process isolation for the SerenaAgent and its language server; they run in a separate process
|
||||
from the MCP server.
|
||||
"""
|
||||
|
||||
def __init__(self, context: str = DEFAULT_CONTEXT, project: str | None = None):
|
||||
"""
|
||||
:param context: The context name or path to context file
|
||||
:param project: Either an absolute path to the project directory or a name of an already registered project.
|
||||
If the project passed here hasn't been registered yet, it will be registered automatically and can be activated by its name
|
||||
afterward.
|
||||
"""
|
||||
super().__init__(context=context, project=project)
|
||||
|
||||
self.active_tool_names: set[str] | None = None
|
||||
self.serena_agent_process: ProcessIsolatedSerenaAgent | None = None
|
||||
self.serena_dashboard_process: ProcessIsolatedDashboard | None = None
|
||||
|
||||
@staticmethod
|
||||
def _determine_active_tool_names(context: SerenaAgentContext, project: Project | None) -> set[str]:
|
||||
"""
|
||||
Determine the names of tools that should be included in this session based on the context.
|
||||
"""
|
||||
tools_excluded_in_this_session = context.get_excluded_tool_classes()
|
||||
|
||||
tools_excluded_in_this_session = context_instance.get_excluded_tool_classes()
|
||||
if is_ide_assistant and project_instance is not None:
|
||||
tools_excluded_in_this_session.extend(project_instance.project_config.get_excluded_tool_classes())
|
||||
# if a project has been loaded, it will be activated at startup and in ide-assistant context,
|
||||
# we assume that no other project will be activated in this session.
|
||||
# Therefore, we exclude the activate project tool
|
||||
tools_excluded_in_this_session.append(ActivateProjectTool)
|
||||
tool_names_excluded_in_this_session = {tool.get_name_from_cls() for tool in tools_excluded_in_this_session}
|
||||
is_ide_assistant = context.name == RegisteredContext.IDE_ASSISTANT.value
|
||||
if is_ide_assistant and project is not None:
|
||||
tools_excluded_in_this_session.extend(project.project_config.get_excluded_tool_classes())
|
||||
tools_excluded_in_this_session.append(ActivateProjectTool)
|
||||
|
||||
all_tool_names = set(ToolRegistry.get_tool_names())
|
||||
tool_names_included_in_this_session = all_tool_names - tool_names_excluded_in_this_session
|
||||
tool_names_excluded_in_this_session = {tool.get_name_from_cls() for tool in tools_excluded_in_this_session}
|
||||
|
||||
try:
|
||||
# Start agent and dashboard processes (the latter only if enabled)
|
||||
serena_dashboard_process = None
|
||||
serena_config = create_serena_config(
|
||||
enable_web_dashboard=enable_web_dashboard,
|
||||
enable_gui_log_window=enable_gui_log_window,
|
||||
log_level=log_level,
|
||||
trace_lsp_communication=trace_lsp_communication,
|
||||
tool_timeout=tool_timeout,
|
||||
)
|
||||
if serena_config.web_dashboard:
|
||||
serena_dashboard_process = ProcessIsolatedDashboard(tool_names=sorted(tool_names_included_in_this_session))
|
||||
serena_agent_process = ProcessIsolatedSerenaAgent(
|
||||
project=project, serena_config=serena_config, modes=modes_instances, context=context_instance
|
||||
all_tool_names = set(ToolRegistry.get_tool_names())
|
||||
tool_names_included_in_this_session = all_tool_names - tool_names_excluded_in_this_session
|
||||
return tool_names_included_in_this_session
|
||||
|
||||
@staticmethod
|
||||
def make_mcp_tool(tool: ToolInterface) -> MCPTool:
|
||||
func_name = tool.get_name()
|
||||
func_doc = tool.get_apply_docstring() or ""
|
||||
func_arg_metadata = tool.get_apply_fn_metadata()
|
||||
is_async = False
|
||||
parameters = func_arg_metadata.arg_model.model_json_schema()
|
||||
|
||||
docstring = docstring_parser.parse(func_doc)
|
||||
|
||||
# Mount the tool description as a combination of the docstring description and
|
||||
# the return value description, if it exists.
|
||||
if docstring.description:
|
||||
func_doc = f"{docstring.description.strip().strip('.')}."
|
||||
else:
|
||||
func_doc = ""
|
||||
if docstring.returns and (docstring_returns_descr := docstring.returns.description):
|
||||
# Only add a space before "Returns" if func_doc is not empty
|
||||
prefix = " " if func_doc else ""
|
||||
func_doc = f"{func_doc}{prefix}Returns {docstring_returns_descr.strip().strip('.')}."
|
||||
|
||||
# Parse the parameter descriptions from the docstring and add pass its description
|
||||
# to the parameter schema.
|
||||
docstring_params = {param.arg_name: param for param in docstring.params}
|
||||
parameters_properties: dict[str, dict[str, Any]] = parameters["properties"]
|
||||
for parameter, properties in parameters_properties.items():
|
||||
if (param_doc := docstring_params.get(parameter)) and param_doc.description:
|
||||
param_desc = f"{param_doc.description.strip().strip('.') + '.'}"
|
||||
properties["description"] = param_desc[0].upper() + param_desc[1:]
|
||||
|
||||
def execute_fn(**kwargs) -> str: # type: ignore
|
||||
return tool.apply_ex(log_call=True, catch_exceptions=True, **kwargs)
|
||||
|
||||
return MCPTool(
|
||||
fn=execute_fn,
|
||||
name=func_name,
|
||||
description=func_doc,
|
||||
parameters=parameters,
|
||||
fn_metadata=func_arg_metadata,
|
||||
is_async=is_async,
|
||||
context_kwarg=None,
|
||||
annotations=None,
|
||||
)
|
||||
|
||||
except Exception as e:
|
||||
show_fatal_exception_safe(e)
|
||||
raise
|
||||
def _iter_tools(self) -> Iterator[ToolInterface]:
|
||||
assert self.active_tool_names is not None
|
||||
assert self.serena_agent_process is not None
|
||||
for tool_name in self.active_tool_names:
|
||||
yield ProcessIsolatedTool(process_agent=self.serena_agent_process, tool_name=tool_name)
|
||||
|
||||
def update_tools() -> None:
|
||||
"""Update the tools in the MCP server - adapted for process isolation."""
|
||||
nonlocal mcp, serena_agent_process
|
||||
|
||||
# Get tool names from agent
|
||||
# Tools may change as a result of project or mode activation.
|
||||
# NOTE: While we could pass updated tool information on to the MCP server via the callback, Claude Desktop does not,
|
||||
# unfortunately, query for changed tools. It only queries for changed resources and prompts regularly,
|
||||
# so we need to register all potentially active tools at startup, unfortunately.
|
||||
# noinspection PyProtectedMember
|
||||
def _set_mcp_tools(self, mcp: FastMCP) -> None:
|
||||
"""Update the tools in the MCP server"""
|
||||
if mcp is not None:
|
||||
mcp._tool_manager._tools = {}
|
||||
for tool_name in tool_names_included_in_this_session:
|
||||
process_isolated_tool = ProcessIsolatedTool(process_agent=serena_agent_process, tool_name=tool_name)
|
||||
mcp_tool = make_tool(process_isolated_tool)
|
||||
mcp._tool_manager._tools[tool_name] = mcp_tool
|
||||
for tool in self._iter_tools():
|
||||
mcp_tool = self.make_mcp_tool(tool)
|
||||
mcp._tool_manager._tools[tool.get_name()] = mcp_tool
|
||||
|
||||
def _instantiate_agent(self, serena_config: SerenaConfig, modes: list[SerenaAgentMode]) -> None:
|
||||
if self.project is not None:
|
||||
self.project_instance = serena_config.get_project(self.project)
|
||||
self.serena_agent_process = ProcessIsolatedSerenaAgent(
|
||||
project=self.project, serena_config=serena_config, modes=modes, context=self.context
|
||||
)
|
||||
self.active_tool_names = self._determine_active_tool_names(self.context, self.project_instance)
|
||||
if serena_config.web_dashboard:
|
||||
assert self.active_tool_names is not None
|
||||
self.serena_dashboard_process = ProcessIsolatedDashboard(tool_names=sorted(self.active_tool_names))
|
||||
|
||||
def create_mcp_server(
|
||||
self,
|
||||
host: str = "0.0.0.0",
|
||||
port: int = 8000,
|
||||
modes: Sequence[str] = DEFAULT_MODES,
|
||||
enable_web_dashboard: bool | None = None,
|
||||
enable_gui_log_window: bool | None = None,
|
||||
log_level: Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] | None = None,
|
||||
trace_lsp_communication: bool | None = None,
|
||||
tool_timeout: float | None = None,
|
||||
) -> FastMCP:
|
||||
"""
|
||||
Create an MCP server with process-isolated SerenaAgent to prevent asyncio contamination.
|
||||
|
||||
:param host: The host to bind to
|
||||
:param port: The port to bind to
|
||||
:param modes: List of mode names or paths to mode files
|
||||
:param enable_web_dashboard: Whether to enable the web dashboard. If not specified, will take the value from the serena configuration.
|
||||
:param enable_gui_log_window: Whether to enable the GUI log window. It currently does not work on macOS, and setting this to True will be ignored then.
|
||||
If not specified, will take the value from the serena configuration.
|
||||
:param log_level: Log level. If not specified, will take the value from the serena configuration.
|
||||
:param trace_lsp_communication: Whether to trace the communication between Serena and the language servers.
|
||||
This is useful for debugging language server issues.
|
||||
:param tool_timeout: Timeout in seconds for tool execution. If not specified, will take the value from the serena configuration.
|
||||
"""
|
||||
try:
|
||||
serena_config = create_serena_config(
|
||||
enable_web_dashboard=enable_web_dashboard,
|
||||
enable_gui_log_window=enable_gui_log_window,
|
||||
log_level=log_level,
|
||||
trace_lsp_communication=trace_lsp_communication,
|
||||
tool_timeout=tool_timeout,
|
||||
)
|
||||
modes_instances = [SerenaAgentMode.load(mode) for mode in modes]
|
||||
self._instantiate_agent(serena_config, modes_instances)
|
||||
|
||||
except Exception as e:
|
||||
show_fatal_exception_safe(e)
|
||||
raise
|
||||
|
||||
# Override model_config to disable the use of `.env` files for reading settings, because user projects are likely to contain
|
||||
# `.env` files (e.g. containing LOG_LEVEL) that are not supposed to override the MCP settings;
|
||||
# retain only FASTMCP_ prefix for already set environment variables.
|
||||
Settings.model_config = SettingsConfigDict(env_prefix="FASTMCP_")
|
||||
|
||||
mcp_settings = Settings(lifespan=self.server_lifespan, host=host, port=port)
|
||||
mcp = FastMCP(**mcp_settings.model_dump())
|
||||
return mcp
|
||||
|
||||
@asynccontextmanager
|
||||
async def server_lifespan(mcp_server: FastMCP) -> AsyncIterator[None]:
|
||||
async def server_lifespan(self, mcp_server: FastMCP) -> AsyncIterator[None]:
|
||||
"""Manage server startup and shutdown lifecycle."""
|
||||
mark_used(mcp_server)
|
||||
|
||||
def signal_handler(signum: int, frame: Any) -> None:
|
||||
log.info(f"Received signal {signum} in main process")
|
||||
@@ -220,6 +393,8 @@ def create_mcp_server_and_agent(
|
||||
def force_exit() -> None:
|
||||
time.sleep(2.0) # Wait 2 seconds for graceful shutdown
|
||||
log.warning("Forcing exit after timeout")
|
||||
# noinspection PyProtectedMember
|
||||
# noinspection PyUnresolvedReferences
|
||||
os._exit(1)
|
||||
|
||||
threading.Thread(target=force_exit, daemon=True).start()
|
||||
@@ -228,12 +403,15 @@ def create_mcp_server_and_agent(
|
||||
sigint_singal = signal.signal(signal.SIGINT, signal_handler)
|
||||
sigterm_signal = signal.signal(signal.SIGTERM, signal_handler)
|
||||
|
||||
if serena_dashboard_process is not None:
|
||||
if self.serena_dashboard_process is not None:
|
||||
log.info("Starting dashboard process")
|
||||
serena_dashboard_process.start()
|
||||
assert self.serena_dashboard_process is not None
|
||||
self.serena_dashboard_process.start()
|
||||
log.info("Starting serena agent process")
|
||||
serena_agent_process.start()
|
||||
update_tools()
|
||||
assert self.serena_agent_process is not None
|
||||
self.serena_agent_process.start()
|
||||
|
||||
self._set_mcp_tools(mcp_server)
|
||||
|
||||
async def monitor_global_shutdown() -> None:
|
||||
"""Monitor the global shutdown event and trigger local shutdown."""
|
||||
@@ -264,23 +442,14 @@ def create_mcp_server_and_agent(
|
||||
with contextlib.suppress(asyncio.CancelledError):
|
||||
await monitor_task
|
||||
|
||||
serena_agent_process.stop()
|
||||
if serena_dashboard_process is not None:
|
||||
serena_dashboard_process.stop()
|
||||
self.serena_agent_process.stop()
|
||||
if self.serena_dashboard_process is not None:
|
||||
self.serena_dashboard_process.stop()
|
||||
request_global_shutdown()
|
||||
log.info("Shutting down all processes")
|
||||
signal.signal(signal.SIGINT, sigint_singal)
|
||||
signal.signal(signal.SIGTERM, sigterm_signal)
|
||||
|
||||
# Override model_config to disable the use of `.env` files for reading settings, because user projects are likely to contain
|
||||
# `.env` files (e.g. containing LOG_LEVEL) that are not supposed to override the MCP settings;
|
||||
# retain only FASTMCP_ prefix for already set environment variables.
|
||||
Settings.model_config = SettingsConfigDict(env_prefix="FASTMCP_")
|
||||
|
||||
mcp_settings = Settings(lifespan=server_lifespan, host=host, port=port)
|
||||
mcp = FastMCP(**mcp_settings.model_dump())
|
||||
return mcp, serena_agent_process
|
||||
|
||||
|
||||
class ProjectType(click.ParamType):
|
||||
name = "[PROJECT_NAME|PROJECT_PATH]"
|
||||
@@ -417,12 +586,17 @@ def start_mcp_server(
|
||||
# This is for backward compatibility with the old CLI, should be removed in the future!
|
||||
project_file = project_file_arg if project_file_arg is not None else project
|
||||
|
||||
mcp_factory: SerenaMCPFactory
|
||||
if USE_SOLID_LSP:
|
||||
mcp_factory = SerenaMCPFactorySingleProcess(context=context, project=project_file)
|
||||
else:
|
||||
# using multilspy requires process isolation to prevent asyncio contamination
|
||||
mcp_factory = SerenaMCPFactoryWithProcessIsolation(context=context, project=project_file)
|
||||
|
||||
# Use process isolation by default to prevent asyncio event loop contamination
|
||||
mcp_server, agent = create_mcp_server_and_agent(
|
||||
project=project_file,
|
||||
mcp_server = mcp_factory.create_mcp_server(
|
||||
host=host,
|
||||
port=port,
|
||||
context=context,
|
||||
modes=modes,
|
||||
enable_web_dashboard=enable_web_dashboard,
|
||||
enable_gui_log_window=enable_gui_log_window,
|
||||
@@ -439,6 +613,6 @@ def start_mcp_server(
|
||||
f"Used path: {project_file}"
|
||||
)
|
||||
|
||||
log.info(f"Starting MCP server with config:\n{agent.serena_config}.\n Log level: {agent.serena_config.log_level}")
|
||||
log.info("Starting MCP server ...")
|
||||
|
||||
mcp_server.run(transport=transport)
|
||||
@@ -3,6 +3,7 @@ import contextlib
|
||||
import logging
|
||||
import multiprocessing
|
||||
import os
|
||||
import threading
|
||||
import traceback
|
||||
import webbrowser
|
||||
from enum import StrEnum
|
||||
@@ -12,7 +13,6 @@ from multiprocessing.sharedctypes import Synchronized
|
||||
from multiprocessing.synchronize import Event as EventClass
|
||||
from typing import Any, Literal, Self
|
||||
|
||||
import uvicorn
|
||||
from mcp.server.fastmcp.utilities.func_metadata import FuncMetadata
|
||||
|
||||
from serena.agent import SerenaAgent, SerenaConfig, SerenaConfigBase, Tool, ToolInterface, ToolRegistry
|
||||
@@ -79,30 +79,21 @@ def _dashboard_worker(
|
||||
port = api._find_first_free_port(0x5EDA)
|
||||
port_value.value = port
|
||||
|
||||
# Start server first, then log processing
|
||||
config = uvicorn.Config(
|
||||
app=api._app,
|
||||
host="0.0.0.0",
|
||||
port=port,
|
||||
workers=1,
|
||||
log_config=None,
|
||||
log_level="critical",
|
||||
)
|
||||
server = uvicorn.Server(config)
|
||||
# Start Flask server in a thread
|
||||
def run_flask_server() -> None:
|
||||
api._app.run(host="0.0.0.0", port=port, debug=False, use_reloader=False, threaded=True)
|
||||
|
||||
server_thread = threading.Thread(target=run_flask_server, daemon=True)
|
||||
server_thread.start()
|
||||
|
||||
# Serve and await shutdown signal
|
||||
server_task = asyncio.create_task(server.serve())
|
||||
shutdown_task = asyncio.create_task(_monitor_shutdown())
|
||||
logging_loop_task = asyncio.create_task(_process_logs(api))
|
||||
dashboard_ready_event.set()
|
||||
|
||||
done, pending = await asyncio.wait(
|
||||
[server_task, shutdown_task, logging_loop_task],
|
||||
[shutdown_task, logging_loop_task],
|
||||
return_when=asyncio.FIRST_COMPLETED,
|
||||
)
|
||||
if shutdown_task in done:
|
||||
server.should_exit = True
|
||||
await server_task
|
||||
|
||||
# Cancel remaining tasks
|
||||
for task in pending:
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
import logging
|
||||
|
||||
from sensai.util.git import GitStatus
|
||||
|
||||
from .shell import subprocess_check_output
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def get_git_status() -> GitStatus | None:
|
||||
try:
|
||||
commit_hash = subprocess_check_output(["git", "rev-parse", "HEAD"])
|
||||
unstaged = bool(subprocess_check_output(["git", "diff", "--name-only"]))
|
||||
staged = bool(subprocess_check_output(["git", "diff", "--staged", "--name-only"]))
|
||||
untracked = bool(subprocess_check_output(["git", "ls-files", "--others", "--exclude-standard"]))
|
||||
return GitStatus(
|
||||
commit=commit_hash, has_unstaged_changes=unstaged, has_staged_uncommitted_changes=staged, has_untracked_files=untracked
|
||||
)
|
||||
except Exception as e:
|
||||
log.error("Error determining Git status", exc_info=e)
|
||||
return None
|
||||
@@ -24,11 +24,14 @@ def execute_shell_command(command: str, cwd: str | None = None, capture_stderr:
|
||||
if cwd is None:
|
||||
cwd = os.getcwd()
|
||||
|
||||
is_windows = platform.system() == "Windows"
|
||||
process = subprocess.Popen(
|
||||
command,
|
||||
shell=platform.system() != "Windows",
|
||||
shell=not is_windows,
|
||||
stdin=subprocess.DEVNULL,
|
||||
stdout=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE if capture_stderr else None,
|
||||
creationflags=subprocess.CREATE_NO_WINDOW if is_windows else 0, # type: ignore
|
||||
text=True,
|
||||
encoding="utf-8",
|
||||
errors="replace",
|
||||
@@ -37,3 +40,18 @@ def execute_shell_command(command: str, cwd: str | None = None, capture_stderr:
|
||||
|
||||
stdout, stderr = process.communicate()
|
||||
return ShellCommandResult(stdout=stdout, stderr=stderr, return_code=process.returncode, cwd=cwd)
|
||||
|
||||
|
||||
def subprocess_check_output(args: list[str], encoding: str = "utf-8", strip: bool = True, timeout: float | None = None) -> str:
|
||||
kwargs = {
|
||||
"stdin": subprocess.DEVNULL,
|
||||
"stderr": subprocess.PIPE,
|
||||
"timeout": timeout,
|
||||
"env": os.environ.copy(),
|
||||
}
|
||||
if platform.system() == "Windows":
|
||||
kwargs["creationflags"] = subprocess.CREATE_NO_WINDOW # type: ignore
|
||||
output = subprocess.check_output(args, **kwargs).decode(encoding) # type: ignore
|
||||
if strip:
|
||||
output = output.strip()
|
||||
return output
|
||||
@@ -0,0 +1,188 @@
|
||||
"""
|
||||
Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_utils import FileUtils
|
||||
from multilspy.multilspy_utils import PlatformUtils
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class ClangdLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides C/C++ specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C/C++.
|
||||
As the project gets bigger in size, building index will take time. Try running clangd multiple times to ensure index is built properly.
|
||||
Also make sure compile_commands.json is created at root of the source directory. Check clangd test case for example.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a ClangdLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
clangd_executable_path = self.setup_runtime_dependencies(logger, config)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=clangd_executable_path, cwd=repository_root_path),
|
||||
"cpp",
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.service_ready_event = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for ClangdLanguageServer.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
assert platform_id.value in [
|
||||
"linux-x64",
|
||||
"win-x64",
|
||||
"osx-arm64",
|
||||
], "Unsupported platform: " + platform_id.value
|
||||
|
||||
runtime_dependencies = d["runtimeDependencies"]
|
||||
runtime_dependencies = [
|
||||
dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value
|
||||
]
|
||||
assert len(runtime_dependencies) == 1
|
||||
# Select dependency matching the current platform
|
||||
dependency = next((dep for dep in runtime_dependencies if dep["platformId"] == platform_id.value), None)
|
||||
if dependency is None:
|
||||
raise RuntimeError(f"No runtime dependency found for platform {platform_id.value}")
|
||||
|
||||
clangd_ls_dir = os.path.join(os.path.dirname(__file__), "static", "clangd")
|
||||
clangd_executable_path = os.path.join(clangd_ls_dir, "clangd_19.1.2", "bin", dependency["binaryName"])
|
||||
if not os.path.exists(clangd_executable_path):
|
||||
clangd_url = dependency["url"]
|
||||
logger.log(f"Clangd executable not found at {clangd_executable_path}. Downloading from {clangd_url}", logging.INFO)
|
||||
os.makedirs(clangd_ls_dir, exist_ok=True)
|
||||
if dependency["archiveType"] == "zip":
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, clangd_url, clangd_ls_dir, dependency["archiveType"]
|
||||
)
|
||||
else:
|
||||
raise RuntimeError(f"Unsupported archive type: {dependency['archiveType']}")
|
||||
if not os.path.exists(clangd_executable_path):
|
||||
raise FileNotFoundError(
|
||||
f"Clangd executable not found at {clangd_executable_path}.\n"
|
||||
"Make sure you have installed clangd. See https://clangd.llvm.org/installation"
|
||||
)
|
||||
os.chmod(clangd_executable_path, stat.S_IEXEC)
|
||||
|
||||
return clangd_executable_path
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the clangd Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r") as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the Clangd Language Server, waits for the server to be ready and yields the LanguageServer instance.
|
||||
|
||||
Usage:
|
||||
```
|
||||
async with lsp.start_server():
|
||||
# LanguageServer has been initialized and ready to serve requests
|
||||
await lsp.request_definition(...)
|
||||
await lsp.request_references(...)
|
||||
# Shutdown the LanguageServer on exit from scope
|
||||
# LanguageServer has been shutdown
|
||||
"""
|
||||
def register_capability_handler(params):
|
||||
assert "registrations" in params
|
||||
for registration in params["registrations"]:
|
||||
if registration["method"] == "workspace/executeCommand":
|
||||
self.initialize_searcher_command_available.set()
|
||||
self.resolve_main_method_available.set()
|
||||
return
|
||||
|
||||
def lang_status_handler(params):
|
||||
# TODO: Should we wait for
|
||||
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
|
||||
# Before proceeding?
|
||||
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
|
||||
self.service_ready_event.set()
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def check_experimental_status(params):
|
||||
if params["quiescent"] == True:
|
||||
self.server_ready.set()
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("language/status", lang_status_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
|
||||
self.logger.log("Starting Clangd server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert init_response["capabilities"]["completionProvider"] == {
|
||||
"triggerCharacters": ['.', '<', '>', ':', '"', '/', '*'],
|
||||
"resolveProvider": False,
|
||||
}
|
||||
|
||||
self.server.notify.initialized({})
|
||||
|
||||
self.completions_available.set()
|
||||
# set ready flag
|
||||
self.server_ready.set()
|
||||
self.server_ready.wait()
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {
|
||||
"didSave": true,
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true
|
||||
}
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": true,
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"_description": "Used to download the runtime dependencies for running Clangd.",
|
||||
"runtimeDependencies": [
|
||||
{
|
||||
"id": "Clangd",
|
||||
"description": "Clangd for Linux (x64)",
|
||||
"url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-linux-19.1.2.zip",
|
||||
"platformId": "linux-x64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "clangd"
|
||||
},
|
||||
{
|
||||
"id": "Clangd",
|
||||
"description": "Clangd for Windows (x64)",
|
||||
"url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-windows-19.1.2.zip",
|
||||
"platformId": "windows-x64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "clangd.exe"
|
||||
},
|
||||
{
|
||||
"id": "Clangd",
|
||||
"description": "Clangd for macOS (Arm64)",
|
||||
"url": "https://github.com/clangd/clangd/releases/download/19.1.2/clangd-mac-19.1.2.zip",
|
||||
"platformId": "osx-arm64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "clangd"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_utils import FileUtils, PlatformUtils
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class DartLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Dart specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Dart.
|
||||
"""
|
||||
|
||||
def __init__(self, config, logger, repository_root_path):
|
||||
"""
|
||||
Creates a DartServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
|
||||
executable_path = self.setup_runtime_dependencies(logger)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path),
|
||||
"dart",
|
||||
)
|
||||
|
||||
def setup_runtime_dependencies(self, logger: "MultilspyLogger") -> str:
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
runtime_dependencies = d["runtimeDependencies"]
|
||||
runtime_dependencies = [
|
||||
dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value
|
||||
]
|
||||
|
||||
assert len(runtime_dependencies) == 1
|
||||
dependency = runtime_dependencies[0]
|
||||
|
||||
dart_ls_dir = os.path.join(os.path.dirname(__file__), "static", "dart-language-server")
|
||||
dart_executable_path = os.path.join(dart_ls_dir, dependency["binaryName"])
|
||||
|
||||
if not os.path.exists(dart_ls_dir):
|
||||
os.makedirs(dart_ls_dir)
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, dependency["url"], dart_ls_dir, dependency["archiveType"]
|
||||
)
|
||||
|
||||
|
||||
assert os.path.exists(dart_executable_path)
|
||||
os.chmod(dart_executable_path, stat.S_IEXEC)
|
||||
|
||||
return f"{dart_executable_path} language-server --client-id multilspy.dart --client-version 1.2"
|
||||
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str):
|
||||
"""
|
||||
Returns the initialize params for the Dart Language Server.
|
||||
"""
|
||||
with open(
|
||||
os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r"
|
||||
) as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(
|
||||
repository_absolute_path
|
||||
).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Start the language server and yield when the server is ready.
|
||||
"""
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def check_experimental_status(params):
|
||||
pass
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
self.server.on_request("client/registerCapability", do_nothing)
|
||||
self.server.on_notification("language/status", do_nothing)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request(
|
||||
"workspace/executeClientCommand", execute_client_command_handler
|
||||
)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
self.server.on_notification(
|
||||
"experimental/serverStatus", check_experimental_status
|
||||
)
|
||||
|
||||
self.logger.log(
|
||||
"Starting dart-language-server server process", logging.INFO
|
||||
)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
self.logger.log(
|
||||
"Sending initialize request to dart-language-server",
|
||||
logging.DEBUG,
|
||||
)
|
||||
init_response = self.server.send_request(
|
||||
"initialize", initialize_params
|
||||
)
|
||||
self.logger.log(
|
||||
f"Received initialize response from dart-language-server: {init_response}",
|
||||
logging.INFO,
|
||||
)
|
||||
|
||||
self.server.notify.initialized({})
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"_description": "This file contains the initialization parameters for the Dart Language Server.",
|
||||
"processId": "$processId",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {},
|
||||
"initializationOptions": {
|
||||
"onlyAnalyzeProjectsWithOpenFiles": false,
|
||||
"suggestFromUnimportedLibraries": true,
|
||||
"closingLabels": false,
|
||||
"outline": false,
|
||||
"flutterOutline": false,
|
||||
"allowOpenUri": false
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
{
|
||||
"_description": "Used to download the runtime dependencies for running Dart Language Server, downloaded from https://dart.dev/get-dart/archive",
|
||||
"runtimeDependencies": [
|
||||
{
|
||||
"id": "DartLanguageServer",
|
||||
"description": "Dart Language Server for Linux (x64)",
|
||||
"url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-linux-x64-release.zip",
|
||||
"platformId": "linux-x64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "dart-sdk/bin/dart"
|
||||
},
|
||||
{
|
||||
"id": "DartLanguageServer",
|
||||
"description": "Dart Language Server for Windows (x64)",
|
||||
"url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-x64-release.zip",
|
||||
"platformId": "win-x64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "dart-sdk/bin/dart.exe"
|
||||
},
|
||||
{
|
||||
"id": "DartLanguageServer",
|
||||
"description": "Dart Language Server for Windows (arm64)",
|
||||
"url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-windows-arm64-release.zip",
|
||||
"platformId": "win-arm64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "dart-sdk/bin/dart.exe"
|
||||
},
|
||||
{
|
||||
"id": "DartLanguageServer",
|
||||
"description": "Dart Language Server for macOS (x64)",
|
||||
"url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-x64-release.zip",
|
||||
"platformId": "osx-x64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "dart-sdk/bin/dart"
|
||||
},
|
||||
{
|
||||
"id": "DartLanguageServer",
|
||||
"description": "Dart Language Server for macOS (arm64)",
|
||||
"url": "https://storage.googleapis.com/dart-archive/channels/stable/release/3.7.1/sdk/dartsdk-macos-arm64-release.zip",
|
||||
"platformId": "osx-arm64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "dart-sdk/bin/dart"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,406 @@
|
||||
"""
|
||||
Provides Java specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Java.
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import stat
|
||||
import threading
|
||||
import uuid
|
||||
from pathlib import PurePath
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_settings import MultilspySettings
|
||||
from multilspy.multilspy_utils import FileUtils
|
||||
from multilspy.multilspy_utils import PlatformUtils
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class RuntimeDependencyPaths:
|
||||
"""
|
||||
Stores the paths to the runtime dependencies of EclipseJDTLS
|
||||
"""
|
||||
|
||||
gradle_path: str
|
||||
lombok_jar_path: str
|
||||
jre_path: str
|
||||
jre_home_path: str
|
||||
jdtls_launcher_jar_path: str
|
||||
jdtls_readonly_config_path: str
|
||||
intellicode_jar_path: str
|
||||
intellisense_members_path: str
|
||||
|
||||
|
||||
class EclipseJDTLS(SolidLanguageServer):
|
||||
"""
|
||||
The EclipseJDTLS class provides a Java specific implementation of the LanguageServer class
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a new EclipseJDTLS instance initializing the language server settings appropriately.
|
||||
This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
|
||||
runtime_dependency_paths = self.setupRuntimeDependencies(logger, config)
|
||||
self.runtime_dependency_paths = runtime_dependency_paths
|
||||
|
||||
# ws_dir is the workspace directory for the EclipseJDTLS server
|
||||
ws_dir = str(
|
||||
PurePath(
|
||||
MultilspySettings.get_language_server_directory(),
|
||||
"EclipseJDTLS",
|
||||
"workspaces",
|
||||
uuid.uuid4().hex,
|
||||
)
|
||||
)
|
||||
|
||||
# shared_cache_location is the global cache used by Eclipse JDTLS across all workspaces
|
||||
shared_cache_location = str(
|
||||
PurePath(MultilspySettings.get_global_cache_directory(), "lsp", "EclipseJDTLS", "sharedIndex")
|
||||
)
|
||||
|
||||
jre_path = self.runtime_dependency_paths.jre_path
|
||||
lombok_jar_path = self.runtime_dependency_paths.lombok_jar_path
|
||||
|
||||
jdtls_launcher_jar = self.runtime_dependency_paths.jdtls_launcher_jar_path
|
||||
|
||||
os.makedirs(ws_dir, exist_ok=True)
|
||||
|
||||
data_dir = str(PurePath(ws_dir, "data_dir"))
|
||||
jdtls_config_path = str(PurePath(ws_dir, "config_path"))
|
||||
|
||||
jdtls_readonly_config_path = self.runtime_dependency_paths.jdtls_readonly_config_path
|
||||
|
||||
if not os.path.exists(jdtls_config_path):
|
||||
shutil.copytree(jdtls_readonly_config_path, jdtls_config_path)
|
||||
|
||||
for static_path in [
|
||||
jre_path,
|
||||
lombok_jar_path,
|
||||
jdtls_launcher_jar,
|
||||
jdtls_config_path,
|
||||
jdtls_readonly_config_path,
|
||||
]:
|
||||
assert os.path.exists(static_path), static_path
|
||||
|
||||
# TODO: Add "self.runtime_dependency_paths.jre_home_path"/bin to $PATH as well
|
||||
proc_env = {"syntaxserver": "false", "JAVA_HOME": self.runtime_dependency_paths.jre_home_path}
|
||||
proc_cwd = repository_root_path
|
||||
cmd = " ".join(
|
||||
[
|
||||
jre_path,
|
||||
"--add-modules=ALL-SYSTEM",
|
||||
"--add-opens",
|
||||
"java.base/java.util=ALL-UNNAMED",
|
||||
"--add-opens",
|
||||
"java.base/java.lang=ALL-UNNAMED",
|
||||
"--add-opens",
|
||||
"java.base/sun.nio.fs=ALL-UNNAMED",
|
||||
"-Declipse.application=org.eclipse.jdt.ls.core.id1",
|
||||
"-Dosgi.bundles.defaultStartLevel=4",
|
||||
"-Declipse.product=org.eclipse.jdt.ls.core.product",
|
||||
"-Djava.import.generatesMetadataFilesAtProjectRoot=false",
|
||||
"-Dfile.encoding=utf8",
|
||||
"-noverify",
|
||||
"-XX:+UseParallelGC",
|
||||
"-XX:GCTimeRatio=4",
|
||||
"-XX:AdaptiveSizePolicyWeight=90",
|
||||
"-Dsun.zip.disableMemoryMapping=true",
|
||||
"-Djava.lsp.joinOnCompletion=true",
|
||||
"-Xmx3G",
|
||||
"-Xms100m",
|
||||
"-Xlog:disable",
|
||||
"-Dlog.level=ALL",
|
||||
f'"-javaagent:{lombok_jar_path}"',
|
||||
f'"-Djdt.core.sharedIndexLocation={shared_cache_location}"',
|
||||
"-jar",
|
||||
f'"{jdtls_launcher_jar}"',
|
||||
"-configuration",
|
||||
f'"{jdtls_config_path}"',
|
||||
"-data",
|
||||
f'"{data_dir}"',
|
||||
]
|
||||
)
|
||||
|
||||
self.service_ready_event = threading.Event()
|
||||
self.intellicode_enable_command_available = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
|
||||
super().__init__(config, logger, repository_root_path, ProcessLaunchInfo(cmd, proc_env, proc_cwd), "java")
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
# Ignore common Java build directories from different build tools:
|
||||
# - Maven: target
|
||||
# - Gradle: build, .gradle
|
||||
# - Eclipse: bin, .settings
|
||||
# - IntelliJ IDEA: out, .idea
|
||||
# - General: classes, dist, lib
|
||||
return super().is_ignored_dirname(dirname) or dirname in [
|
||||
"target", # Maven
|
||||
"build", # Gradle
|
||||
"bin", # Eclipse
|
||||
"out", # IntelliJ IDEA
|
||||
"classes", # General
|
||||
"dist", # General
|
||||
"lib" # General
|
||||
]
|
||||
|
||||
def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> RuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for EclipseJDTLS.
|
||||
"""
|
||||
platformId = PlatformUtils.get_platform_id()
|
||||
|
||||
with open(str(PurePath(os.path.dirname(__file__), "runtime_dependencies.json")), "r", encoding="utf-8") as f:
|
||||
runtimeDependencies = json.load(f)
|
||||
del runtimeDependencies["_description"]
|
||||
|
||||
os.makedirs(str(PurePath(os.path.abspath(os.path.dirname(__file__)), "static")), exist_ok=True)
|
||||
|
||||
# assert platformId.value in [
|
||||
# "linux-x64",
|
||||
# "win-x64",
|
||||
# ], "Only linux-x64 platform is supported for in multilspy at the moment"
|
||||
|
||||
gradle_path = str(
|
||||
PurePath(
|
||||
os.path.abspath(os.path.dirname(__file__)),
|
||||
"static/gradle-7.3.3",
|
||||
)
|
||||
)
|
||||
|
||||
if not os.path.exists(gradle_path):
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger,
|
||||
runtimeDependencies["gradle"]["platform-agnostic"]["url"],
|
||||
str(PurePath(gradle_path).parent),
|
||||
runtimeDependencies["gradle"]["platform-agnostic"]["archiveType"],
|
||||
)
|
||||
|
||||
assert os.path.exists(gradle_path)
|
||||
|
||||
dependency = runtimeDependencies["vscode-java"][platformId.value]
|
||||
vscode_java_path = str(
|
||||
PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"])
|
||||
)
|
||||
os.makedirs(vscode_java_path, exist_ok=True)
|
||||
jre_home_path = str(PurePath(vscode_java_path, dependency["jre_home_path"]))
|
||||
jre_path = str(PurePath(vscode_java_path, dependency["jre_path"]))
|
||||
lombok_jar_path = str(PurePath(vscode_java_path, dependency["lombok_jar_path"]))
|
||||
jdtls_launcher_jar_path = str(PurePath(vscode_java_path, dependency["jdtls_launcher_jar_path"]))
|
||||
jdtls_readonly_config_path = str(PurePath(vscode_java_path, dependency["jdtls_readonly_config_path"]))
|
||||
if not all(
|
||||
[
|
||||
os.path.exists(vscode_java_path),
|
||||
os.path.exists(jre_home_path),
|
||||
os.path.exists(jre_path),
|
||||
os.path.exists(lombok_jar_path),
|
||||
os.path.exists(jdtls_launcher_jar_path),
|
||||
os.path.exists(jdtls_readonly_config_path),
|
||||
]
|
||||
):
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, dependency["url"], vscode_java_path, dependency["archiveType"]
|
||||
)
|
||||
|
||||
os.chmod(jre_path, stat.S_IEXEC)
|
||||
|
||||
assert os.path.exists(vscode_java_path)
|
||||
assert os.path.exists(jre_home_path)
|
||||
assert os.path.exists(jre_path)
|
||||
assert os.path.exists(lombok_jar_path)
|
||||
assert os.path.exists(jdtls_launcher_jar_path)
|
||||
assert os.path.exists(jdtls_readonly_config_path)
|
||||
|
||||
dependency = runtimeDependencies["intellicode"]["platform-agnostic"]
|
||||
intellicode_directory_path = str(
|
||||
PurePath(os.path.abspath(os.path.dirname(__file__)), "static", dependency["relative_extraction_path"])
|
||||
)
|
||||
os.makedirs(intellicode_directory_path, exist_ok=True)
|
||||
intellicode_jar_path = str(PurePath(intellicode_directory_path, dependency["intellicode_jar_path"]))
|
||||
intellisense_members_path = str(PurePath(intellicode_directory_path, dependency["intellisense_members_path"]))
|
||||
if not all(
|
||||
[
|
||||
os.path.exists(intellicode_directory_path),
|
||||
os.path.exists(intellicode_jar_path),
|
||||
os.path.exists(intellisense_members_path),
|
||||
]
|
||||
):
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, dependency["url"], intellicode_directory_path, dependency["archiveType"]
|
||||
)
|
||||
|
||||
assert os.path.exists(intellicode_directory_path)
|
||||
assert os.path.exists(intellicode_jar_path)
|
||||
assert os.path.exists(intellisense_members_path)
|
||||
|
||||
return RuntimeDependencyPaths(
|
||||
gradle_path=gradle_path,
|
||||
lombok_jar_path=lombok_jar_path,
|
||||
jre_path=jre_path,
|
||||
jre_home_path=jre_home_path,
|
||||
jdtls_launcher_jar_path=jdtls_launcher_jar_path,
|
||||
jdtls_readonly_config_path=jdtls_readonly_config_path,
|
||||
intellicode_jar_path=intellicode_jar_path,
|
||||
intellisense_members_path=intellisense_members_path,
|
||||
)
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize parameters for the EclipseJDTLS server.
|
||||
"""
|
||||
# Look into https://github.com/eclipse/eclipse.jdt.ls/blob/master/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java to understand all the options available
|
||||
with open(str(PurePath(os.path.dirname(__file__), "initialize_params.json")), "r", encoding="utf-8") as f:
|
||||
d: InitializeParams = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
if not os.path.isabs(repository_absolute_path):
|
||||
repository_absolute_path = os.path.abspath(repository_absolute_path)
|
||||
|
||||
assert d["processId"] == "os.getpid()"
|
||||
d["processId"] = os.getpid()
|
||||
|
||||
assert d["rootPath"] == "repository_absolute_path"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "pathlib.Path(repository_absolute_path).as_uri()"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["initializationOptions"]["workspaceFolders"] == "[pathlib.Path(repository_absolute_path).as_uri()]"
|
||||
d["initializationOptions"]["workspaceFolders"] = [pathlib.Path(repository_absolute_path).as_uri()]
|
||||
|
||||
assert (
|
||||
d["workspaceFolders"]
|
||||
== '[\n {\n "uri": pathlib.Path(repository_absolute_path).as_uri(),\n "name": os.path.basename(repository_absolute_path),\n }\n ]'
|
||||
)
|
||||
d["workspaceFolders"] = [
|
||||
{
|
||||
"uri": pathlib.Path(repository_absolute_path).as_uri(),
|
||||
"name": os.path.basename(repository_absolute_path),
|
||||
}
|
||||
]
|
||||
|
||||
assert d["initializationOptions"]["bundles"] == ["intellicode-core.jar"]
|
||||
bundles = [self.runtime_dependency_paths.intellicode_jar_path]
|
||||
d["initializationOptions"]["bundles"] = bundles
|
||||
|
||||
assert d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] == [
|
||||
{"name": "JavaSE-21", "path": "static/vscode-java/extension/jre/21.0.7-linux-x86_64", "default": True}
|
||||
]
|
||||
d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"] = [
|
||||
{"name": "JavaSE-21", "path": self.runtime_dependency_paths.jre_home_path, "default": True}
|
||||
]
|
||||
|
||||
for runtime in d["initializationOptions"]["settings"]["java"]["configuration"]["runtimes"]:
|
||||
assert "name" in runtime
|
||||
assert "path" in runtime
|
||||
assert os.path.exists(
|
||||
runtime["path"]
|
||||
), f"Runtime required for eclipse_jdtls at path {runtime['path']} does not exist"
|
||||
|
||||
assert d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["home"] == "abs(static/gradle-7.3.3)"
|
||||
d["initializationOptions"]["settings"]["java"]["import"]["gradle"][
|
||||
"home"
|
||||
] = self.runtime_dependency_paths.gradle_path
|
||||
|
||||
d["initializationOptions"]["settings"]["java"]["import"]["gradle"]["java"][
|
||||
"home"
|
||||
] = self.runtime_dependency_paths.jre_path
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the Eclipse JDTLS Language Server
|
||||
"""
|
||||
|
||||
def register_capability_handler(params):
|
||||
assert "registrations" in params
|
||||
for registration in params["registrations"]:
|
||||
if registration["method"] == "textDocument/completion":
|
||||
assert registration["registerOptions"]["resolveProvider"] == True
|
||||
assert registration["registerOptions"]["triggerCharacters"] == [
|
||||
".",
|
||||
"@",
|
||||
"#",
|
||||
"*",
|
||||
" ",
|
||||
]
|
||||
self.completions_available.set()
|
||||
if registration["method"] == "workspace/executeCommand":
|
||||
if "java.intellicode.enable" in registration["registerOptions"]["commands"]:
|
||||
self.intellicode_enable_command_available.set()
|
||||
return
|
||||
|
||||
def lang_status_handler(params):
|
||||
# TODO: Should we wait for
|
||||
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
|
||||
# Before proceeding?
|
||||
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
|
||||
self.service_ready_event.set()
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
assert params["command"] == "_java.reloadBundles.command"
|
||||
assert params["arguments"] == []
|
||||
return []
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("language/status", lang_status_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
|
||||
self.logger.log("Starting EclipseJDTLS server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
|
||||
assert "completionProvider" not in init_response["capabilities"]
|
||||
assert "executeCommandProvider" not in init_response["capabilities"]
|
||||
|
||||
self.server.notify.initialized({})
|
||||
|
||||
self.server.notify.workspace_did_change_configuration(
|
||||
{"settings": initialize_params["initializationOptions"]["settings"]}
|
||||
)
|
||||
|
||||
self.intellicode_enable_command_available.wait()
|
||||
|
||||
java_intellisense_members_path = self.runtime_dependency_paths.intellisense_members_path
|
||||
assert os.path.exists(java_intellisense_members_path)
|
||||
intellicode_enable_result = self.server.send.execute_command(
|
||||
{
|
||||
"command": "java.intellicode.enable",
|
||||
"arguments": [True, java_intellisense_members_path],
|
||||
}
|
||||
)
|
||||
assert intellicode_enable_result
|
||||
|
||||
# TODO: Add comments about why we wait here, and how this can be optimized
|
||||
self.service_ready_event.wait()
|
||||
@@ -0,0 +1,849 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"clientInfo": {
|
||||
"name": "Visual Studio Code - Insiders",
|
||||
"version": "1.77.0-insider"
|
||||
},
|
||||
"locale": "en",
|
||||
"rootPath": "repository_absolute_path",
|
||||
"rootUri": "pathlib.Path(repository_absolute_path).as_uri()",
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": true,
|
||||
"workspaceEdit": {
|
||||
"documentChanges": true,
|
||||
"resourceOperations": [
|
||||
"create",
|
||||
"rename",
|
||||
"delete"
|
||||
],
|
||||
"failureHandling": "textOnlyTransactional",
|
||||
"normalizesLineEndings": true,
|
||||
"changeAnnotationSupport": {
|
||||
"groupsOnLabel": true
|
||||
}
|
||||
},
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": true,
|
||||
"relativePatternSupport": true
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"location.range"
|
||||
]
|
||||
}
|
||||
},
|
||||
"codeLens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"configuration": true,
|
||||
"workspaceFolders": true,
|
||||
"semanticTokens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"fileOperations": {
|
||||
"dynamicRegistration": true,
|
||||
"didCreate": true,
|
||||
"didRename": true,
|
||||
"didDelete": true,
|
||||
"willCreate": true,
|
||||
"willRename": true,
|
||||
"willDelete": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"refreshSupport": true
|
||||
}
|
||||
},
|
||||
"textDocument": {
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": true,
|
||||
"versionSupport": false,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"codeDescriptionSupport": true,
|
||||
"dataSupport": true
|
||||
},
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"willSave": true,
|
||||
"willSaveWaitUntil": true,
|
||||
"didSave": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"contextSupport": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": false,
|
||||
"commitCharactersSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"deprecatedSupport": true,
|
||||
"preselectSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"insertReplaceSupport": false,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"documentation",
|
||||
"detail",
|
||||
"additionalTextEdits"
|
||||
]
|
||||
},
|
||||
"insertTextModeSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"labelDetailsSupport": true
|
||||
},
|
||||
"insertTextMode": 2,
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"completionList": {
|
||||
"itemDefaults": [
|
||||
"commitCharacters",
|
||||
"editRange",
|
||||
"insertTextFormat",
|
||||
"insertTextMode"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": true,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"parameterInformation": {
|
||||
"labelOffsetSupport": true
|
||||
},
|
||||
"activeParameterSupport": true
|
||||
},
|
||||
"contextSupport": true
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"labelSupport": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"isPreferredSupport": true,
|
||||
"disabledSupport": true,
|
||||
"dataSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
},
|
||||
"honorsChangeAnnotations": false
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": true,
|
||||
"prepareSupport": true,
|
||||
"prepareSupportDefaultBehavior": 1,
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": true,
|
||||
"tooltipSupport": true
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"foldingRange": {
|
||||
"dynamicRegistration": true,
|
||||
"rangeLimit": 5000,
|
||||
"lineFoldingOnly": true,
|
||||
"foldingRangeKind": {
|
||||
"valueSet": [
|
||||
"comment",
|
||||
"imports",
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"foldingRange": {
|
||||
"collapsedText": false
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"selectionRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"callHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"dynamicRegistration": true,
|
||||
"tokenTypes": [
|
||||
"namespace",
|
||||
"type",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"struct",
|
||||
"typeParameter",
|
||||
"parameter",
|
||||
"variable",
|
||||
"property",
|
||||
"enumMember",
|
||||
"event",
|
||||
"function",
|
||||
"method",
|
||||
"macro",
|
||||
"keyword",
|
||||
"modifier",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"decorator"
|
||||
],
|
||||
"tokenModifiers": [
|
||||
"declaration",
|
||||
"definition",
|
||||
"readonly",
|
||||
"static",
|
||||
"deprecated",
|
||||
"abstract",
|
||||
"async",
|
||||
"modification",
|
||||
"documentation",
|
||||
"defaultLibrary"
|
||||
],
|
||||
"formats": [
|
||||
"relative"
|
||||
],
|
||||
"requests": {
|
||||
"range": true,
|
||||
"full": {
|
||||
"delta": true
|
||||
}
|
||||
},
|
||||
"multilineTokenSupport": false,
|
||||
"overlappingTokenSupport": false,
|
||||
"serverCancelSupport": true,
|
||||
"augmentsSyntaxTokens": true
|
||||
},
|
||||
"linkedEditingRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"dynamicRegistration": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"tooltip",
|
||||
"textEdits",
|
||||
"label.tooltip",
|
||||
"label.location",
|
||||
"label.command"
|
||||
]
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"dynamicRegistration": true,
|
||||
"relatedDocumentSupport": false
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"showMessage": {
|
||||
"messageActionItem": {
|
||||
"additionalPropertiesSupport": true
|
||||
}
|
||||
},
|
||||
"showDocument": {
|
||||
"support": true
|
||||
},
|
||||
"workDoneProgress": true
|
||||
},
|
||||
"general": {
|
||||
"staleRequestSupport": {
|
||||
"cancel": true,
|
||||
"retryOnContentModified": [
|
||||
"textDocument/semanticTokens/full",
|
||||
"textDocument/semanticTokens/range",
|
||||
"textDocument/semanticTokens/full/delta"
|
||||
]
|
||||
},
|
||||
"regularExpressions": {
|
||||
"engine": "ECMAScript",
|
||||
"version": "ES2020"
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "marked",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"positionEncodings": [
|
||||
"utf-16"
|
||||
]
|
||||
},
|
||||
"notebookDocument": {
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"executionSummarySupport": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"initializationOptions": {
|
||||
"bundles": [
|
||||
"intellicode-core.jar"
|
||||
],
|
||||
"workspaceFolders": "[pathlib.Path(repository_absolute_path).as_uri()]",
|
||||
"settings": {
|
||||
"java": {
|
||||
"home": null,
|
||||
"jdt": {
|
||||
"ls": {
|
||||
"java": {
|
||||
"home": null
|
||||
},
|
||||
"vmargs": "-XX:+UseParallelGC -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -Dsun.zip.disableMemoryMapping=true -Xmx1G -Xms100m -Xlog:disable",
|
||||
"lombokSupport": {
|
||||
"enabled": true
|
||||
},
|
||||
"protobufSupport": {
|
||||
"enabled": true
|
||||
},
|
||||
"androidSupport": {
|
||||
"enabled": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"errors": {
|
||||
"incompleteClasspath": {
|
||||
"severity": "error"
|
||||
}
|
||||
},
|
||||
"configuration": {
|
||||
"checkProjectSettingsExclusions": false,
|
||||
"updateBuildConfiguration": "interactive",
|
||||
"maven": {
|
||||
"userSettings": null,
|
||||
"globalSettings": null,
|
||||
"notCoveredPluginExecutionSeverity": "warning",
|
||||
"defaultMojoExecutionAction": "ignore"
|
||||
},
|
||||
"workspaceCacheLimit": 90,
|
||||
"runtimes": [
|
||||
{
|
||||
"name": "JavaSE-21",
|
||||
"path": "static/vscode-java/extension/jre/21.0.7-linux-x86_64",
|
||||
"default": true
|
||||
}
|
||||
]
|
||||
},
|
||||
"trace": {
|
||||
"server": "verbose"
|
||||
},
|
||||
"import": {
|
||||
"maven": {
|
||||
"enabled": true,
|
||||
"offline": {
|
||||
"enabled": false
|
||||
},
|
||||
"disableTestClasspathFlag": false
|
||||
},
|
||||
"gradle": {
|
||||
"enabled": true,
|
||||
"wrapper": {
|
||||
"enabled": true
|
||||
},
|
||||
"version": null,
|
||||
"home": "abs(static/gradle-7.3.3)",
|
||||
"java": {
|
||||
"home": "abs(static/launch_jres/21.0.7-linux-x86_64)"
|
||||
},
|
||||
"offline": {
|
||||
"enabled": false
|
||||
},
|
||||
"arguments": null,
|
||||
"jvmArguments": null,
|
||||
"user": {
|
||||
"home": null
|
||||
},
|
||||
"annotationProcessing": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"exclusions": [
|
||||
"**/node_modules/**",
|
||||
"**/.metadata/**",
|
||||
"**/archetype-resources/**",
|
||||
"**/META-INF/maven/**"
|
||||
],
|
||||
"generatesMetadataFilesAtProjectRoot": false
|
||||
},
|
||||
"maven": {
|
||||
"downloadSources": true,
|
||||
"updateSnapshots": true
|
||||
},
|
||||
"eclipse": {
|
||||
"downloadSources": true
|
||||
},
|
||||
"referencesCodeLens": {
|
||||
"enabled": true
|
||||
},
|
||||
"signatureHelp": {
|
||||
"enabled": true,
|
||||
"description": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"implementationsCodeLens": {
|
||||
"enabled": true
|
||||
},
|
||||
"format": {
|
||||
"enabled": true,
|
||||
"settings": {
|
||||
"url": null,
|
||||
"profile": null
|
||||
},
|
||||
"comments": {
|
||||
"enabled": true
|
||||
},
|
||||
"onType": {
|
||||
"enabled": true
|
||||
},
|
||||
"insertSpaces": true,
|
||||
"tabSize": 4
|
||||
},
|
||||
"saveActions": {
|
||||
"organizeImports": false
|
||||
},
|
||||
"project": {
|
||||
"referencedLibraries": [
|
||||
"lib/**/*.jar"
|
||||
],
|
||||
"importOnFirstTimeStartup": "automatic",
|
||||
"importHint": true,
|
||||
"resourceFilters": [
|
||||
"node_modules",
|
||||
"\\.git"
|
||||
],
|
||||
"encoding": "ignore",
|
||||
"exportJar": {
|
||||
"targetPath": "${workspaceFolder}/${workspaceFolderBasename}.jar"
|
||||
}
|
||||
},
|
||||
"contentProvider": {
|
||||
"preferred": null
|
||||
},
|
||||
"autobuild": {
|
||||
"enabled": true
|
||||
},
|
||||
"maxConcurrentBuilds": 1,
|
||||
"recommendations": {
|
||||
"dependency": {
|
||||
"analytics": {
|
||||
"show": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"completion": {
|
||||
"maxResults": 0,
|
||||
"enabled": true,
|
||||
"guessMethodArguments": true,
|
||||
"favoriteStaticMembers": [
|
||||
"org.junit.Assert.*",
|
||||
"org.junit.Assume.*",
|
||||
"org.junit.jupiter.api.Assertions.*",
|
||||
"org.junit.jupiter.api.Assumptions.*",
|
||||
"org.junit.jupiter.api.DynamicContainer.*",
|
||||
"org.junit.jupiter.api.DynamicTest.*",
|
||||
"org.mockito.Mockito.*",
|
||||
"org.mockito.ArgumentMatchers.*",
|
||||
"org.mockito.Answers.*"
|
||||
],
|
||||
"filteredTypes": [
|
||||
"java.awt.*",
|
||||
"com.sun.*",
|
||||
"sun.*",
|
||||
"jdk.*",
|
||||
"org.graalvm.*",
|
||||
"io.micrometer.shaded.*"
|
||||
],
|
||||
"importOrder": [
|
||||
"#",
|
||||
"java",
|
||||
"javax",
|
||||
"org",
|
||||
"com",
|
||||
""
|
||||
],
|
||||
"postfix": {
|
||||
"enabled": false
|
||||
},
|
||||
"matchCase": "off"
|
||||
},
|
||||
"foldingRange": {
|
||||
"enabled": true
|
||||
},
|
||||
"progressReports": {
|
||||
"enabled": false
|
||||
},
|
||||
"codeGeneration": {
|
||||
"hashCodeEquals": {
|
||||
"useJava7Objects": false,
|
||||
"useInstanceof": false
|
||||
},
|
||||
"useBlocks": false,
|
||||
"generateComments": false,
|
||||
"toString": {
|
||||
"template": "${object.className} [${member.name()}=${member.value}, ${otherMembers}]",
|
||||
"codeStyle": "STRING_CONCATENATION",
|
||||
"skipNullValues": false,
|
||||
"listArrayContents": true,
|
||||
"limitElements": 0
|
||||
},
|
||||
"insertionLocation": "afterCursor"
|
||||
},
|
||||
"selectionRange": {
|
||||
"enabled": true
|
||||
},
|
||||
"showBuildStatusOnStart": {
|
||||
"enabled": "notification"
|
||||
},
|
||||
"server": {
|
||||
"launchMode": "Standard"
|
||||
},
|
||||
"sources": {
|
||||
"organizeImports": {
|
||||
"starThreshold": 99,
|
||||
"staticStarThreshold": 99
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"gradle": {
|
||||
"wrapper": {
|
||||
"checksums": []
|
||||
}
|
||||
}
|
||||
},
|
||||
"templates": {
|
||||
"fileHeader": [],
|
||||
"typeComment": []
|
||||
},
|
||||
"references": {
|
||||
"includeAccessors": true,
|
||||
"includeDecompiledSources": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"lazyLoad": false
|
||||
},
|
||||
"settings": {
|
||||
"url": null
|
||||
},
|
||||
"symbols": {
|
||||
"includeSourceMethodDeclarations": false
|
||||
},
|
||||
"quickfix": {
|
||||
"showAt": "line"
|
||||
},
|
||||
"inlayHints": {
|
||||
"parameterNames": {
|
||||
"enabled": "literals",
|
||||
"exclusions": []
|
||||
}
|
||||
},
|
||||
"codeAction": {
|
||||
"sortMembers": {
|
||||
"avoidVolatileChanges": true
|
||||
}
|
||||
},
|
||||
"compile": {
|
||||
"nullAnalysis": {
|
||||
"nonnull": [
|
||||
"javax.annotation.Nonnull",
|
||||
"org.eclipse.jdt.annotation.NonNull",
|
||||
"org.springframework.lang.NonNull"
|
||||
],
|
||||
"nullable": [
|
||||
"javax.annotation.Nullable",
|
||||
"org.eclipse.jdt.annotation.Nullable",
|
||||
"org.springframework.lang.Nullable"
|
||||
],
|
||||
"mode": "automatic"
|
||||
}
|
||||
},
|
||||
"cleanup": {
|
||||
"actionsOnSave": []
|
||||
},
|
||||
"sharedIndexes": {
|
||||
"enabled": "auto",
|
||||
"location": ""
|
||||
},
|
||||
"refactoring": {
|
||||
"extract": {
|
||||
"interface": {
|
||||
"replace": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"debug": {
|
||||
"logLevel": "verbose",
|
||||
"settings": {
|
||||
"showHex": false,
|
||||
"showStaticVariables": false,
|
||||
"showQualifiedNames": false,
|
||||
"showLogicalStructure": true,
|
||||
"showToString": true,
|
||||
"maxStringLength": 0,
|
||||
"numericPrecision": 0,
|
||||
"hotCodeReplace": "manual",
|
||||
"enableRunDebugCodeLens": true,
|
||||
"forceBuildBeforeLaunch": true,
|
||||
"onBuildFailureProceed": false,
|
||||
"console": "integratedTerminal",
|
||||
"exceptionBreakpoint": {
|
||||
"skipClasses": []
|
||||
},
|
||||
"stepping": {
|
||||
"skipClasses": [],
|
||||
"skipSynthetics": false,
|
||||
"skipStaticInitializers": false,
|
||||
"skipConstructors": false
|
||||
},
|
||||
"jdwp": {
|
||||
"limitOfVariablesPerJdwpRequest": 100,
|
||||
"requestTimeout": 3000,
|
||||
"async": "auto"
|
||||
},
|
||||
"vmArgs": ""
|
||||
}
|
||||
},
|
||||
"silentNotification": false,
|
||||
"dependency": {
|
||||
"showMembers": false,
|
||||
"syncWithFolderExplorer": true,
|
||||
"autoRefresh": true,
|
||||
"refreshDelay": 2000,
|
||||
"packagePresentation": "flat"
|
||||
},
|
||||
"help": {
|
||||
"firstView": "auto",
|
||||
"showReleaseNotes": true,
|
||||
"collectErrorLog": false
|
||||
},
|
||||
"test": {
|
||||
"defaultConfig": "",
|
||||
"config": {}
|
||||
}
|
||||
}
|
||||
},
|
||||
"extendedClientCapabilities": {
|
||||
"progressReportProvider": false,
|
||||
"classFileContentsSupport": true,
|
||||
"overrideMethodsPromptSupport": true,
|
||||
"hashCodeEqualsPromptSupport": true,
|
||||
"advancedOrganizeImportsSupport": true,
|
||||
"generateToStringPromptSupport": true,
|
||||
"advancedGenerateAccessorsSupport": true,
|
||||
"generateConstructorsPromptSupport": true,
|
||||
"generateDelegateMethodsPromptSupport": true,
|
||||
"advancedExtractRefactoringSupport": true,
|
||||
"inferSelectionSupport": [
|
||||
"extractMethod",
|
||||
"extractVariable",
|
||||
"extractField"
|
||||
],
|
||||
"moveRefactoringSupport": true,
|
||||
"clientHoverProvider": true,
|
||||
"clientDocumentSymbolProvider": true,
|
||||
"gradleChecksumWrapperPromptSupport": true,
|
||||
"resolveAdditionalTextEditsSupport": true,
|
||||
"advancedIntroduceParameterRefactoringSupport": true,
|
||||
"actionableRuntimeNotificationSupport": true,
|
||||
"shouldLanguageServerExitOnShutdown": true,
|
||||
"onCompletionItemSelectedCommand": "editor.action.triggerParameterHints",
|
||||
"extractInterfaceSupport": true,
|
||||
"advancedUpgradeGradleSupport": true
|
||||
},
|
||||
"triggerFiles": []
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": "[\n {\n \"uri\": pathlib.Path(repository_absolute_path).as_uri(),\n \"name\": os.path.basename(repository_absolute_path),\n }\n ]"
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
{
|
||||
"_description": "This file lists the runtime dependencies for the Java Language Server",
|
||||
"gradle": {
|
||||
"platform-agnostic": {
|
||||
"url": "https://services.gradle.org/distributions/gradle-7.3.3-bin.zip",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "."
|
||||
}
|
||||
},
|
||||
"vscode-java": {
|
||||
"darwin-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java"
|
||||
},
|
||||
"osx-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"jre_home_path": "extension/jre/21.0.7-macosx-aarch64",
|
||||
"jre_path": "extension/jre/21.0.7-macosx-aarch64/bin/java",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
"jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
|
||||
"jdtls_readonly_config_path": "extension/server/config_mac_arm"
|
||||
},
|
||||
"osx-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"jre_home_path": "extension/jre/21.0.7-macosx-x86_64",
|
||||
"jre_path": "extension/jre/21.0.7-macosx-x86_64/bin/java",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
"jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
|
||||
"jdtls_readonly_config_path": "extension/server/config_mac"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java"
|
||||
},
|
||||
"linux-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"jre_home_path": "extension/jre/21.0.7-linux-x86_64",
|
||||
"jre_path": "extension/jre/21.0.7-linux-x86_64/bin/java",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
"jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
|
||||
"jdtls_readonly_config_path": "extension/server/config_linux"
|
||||
},
|
||||
"win-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "vscode-java",
|
||||
"jre_home_path": "extension/jre/21.0.7-win32-x86_64",
|
||||
"jre_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe",
|
||||
"lombok_jar_path": "extension/lombok/lombok-1.18.36.jar",
|
||||
"jdtls_launcher_jar_path": "extension/server/plugins/org.eclipse.equinox.launcher_1.7.0.v20250424-1814.jar",
|
||||
"jdtls_readonly_config_path": "extension/server/config_win"
|
||||
}
|
||||
},
|
||||
"intellicode": {
|
||||
"platform-agnostic": {
|
||||
"url": "https://VisualStudioExptTeam.gallery.vsassets.io/_apis/public/gallery/publisher/VisualStudioExptTeam/extension/vscodeintellicode/1.2.30/assetbyname/Microsoft.VisualStudio.Services.VSIXPackage",
|
||||
"alternate_url": "https://marketplace.visualstudio.com/_apis/public/gallery/publishers/VisualStudioExptTeam/vsextensions/vscodeintellicode/1.2.30/vspackage",
|
||||
"archiveType": "zip",
|
||||
"relative_extraction_path": "intellicode",
|
||||
"intellicode_jar_path": "extension/dist/com.microsoft.jdtls.intellicode.core-0.7.0.jar",
|
||||
"intellisense_members_path": "extension/dist/bundledModels/java_intellisense-members"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,145 @@
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import threading
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class Gopls(SolidLanguageServer):
|
||||
"""
|
||||
Provides Go specific instantiation of the LanguageServer class using gopls.
|
||||
"""
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
# For Go projects, we should ignore:
|
||||
# - vendor: third-party dependencies vendored into the project
|
||||
# - node_modules: if the project has JavaScript components
|
||||
# - dist/build: common output directories
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["vendor", "node_modules", "dist", "build"]
|
||||
|
||||
@staticmethod
|
||||
def _get_go_version():
|
||||
"""Get the installed Go version or None if not found."""
|
||||
try:
|
||||
result = subprocess.run(['go', 'version'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _get_gopls_version():
|
||||
"""Get the installed gopls version or None if not found."""
|
||||
try:
|
||||
result = subprocess.run(['gopls', 'version'], capture_output=True, text=True)
|
||||
if result.returncode == 0:
|
||||
return result.stdout.strip()
|
||||
except FileNotFoundError:
|
||||
return None
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def setup_runtime_dependency(cls):
|
||||
"""
|
||||
Check if required Go runtime dependencies are available.
|
||||
Raises RuntimeError with helpful message if dependencies are missing.
|
||||
"""
|
||||
go_version = cls._get_go_version()
|
||||
if not go_version:
|
||||
raise RuntimeError("Go is not installed. Please install Go from https://golang.org/doc/install and make sure it is added to your PATH.")
|
||||
|
||||
gopls_version = cls._get_gopls_version()
|
||||
if not gopls_version:
|
||||
raise RuntimeError(
|
||||
"Found a Go version but gopls is not installed.\n"
|
||||
"Please install gopls as described in https://pkg.go.dev/golang.org/x/tools/gopls#section-readme\n\n"
|
||||
"After installation, make sure it is added to your PATH (it might be installed in a different location than Go)."
|
||||
)
|
||||
|
||||
return True
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
self.setup_runtime_dependency()
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd="gopls", cwd=repository_root_path),
|
||||
"go",
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.request_id = 0
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the TypeScript Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""Start gopls server process"""
|
||||
def register_capability_handler(params):
|
||||
return
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_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)
|
||||
|
||||
self.logger.log("Starting gopls server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
# Verify server capabilities
|
||||
assert "textDocumentSync" in init_response["capabilities"]
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert "definitionProvider" in init_response["capabilities"]
|
||||
|
||||
self.server.notify.initialized({})
|
||||
self.completions_available.set()
|
||||
|
||||
# gopls server is typically ready immediately after initialization
|
||||
self.server_ready.set()
|
||||
self.server_ready.wait()
|
||||
@@ -0,0 +1,46 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {
|
||||
"didSave": true,
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true
|
||||
}
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,
|
||||
14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": true,
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
"textDocument": {
|
||||
"synchronization": {
|
||||
"didSave": true,
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true
|
||||
}
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"workspaceFolders": true,
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,192 @@
|
||||
"""
|
||||
Provides PHP specific instantiation of the LanguageServer class using Intelephense.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
from time import sleep
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import DefinitionParams, InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_utils import PlatformUtils, PlatformId
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class Intelephense(SolidLanguageServer):
|
||||
"""
|
||||
Provides PHP specific instantiation of the LanguageServer class using Intelephense.
|
||||
"""
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
# For PHP projects, we should ignore:
|
||||
# - vendor: third-party dependencies managed by Composer
|
||||
# - node_modules: if the project has JavaScript components
|
||||
# - cache: commonly used for caching
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["node_modules", "vendor", "cache"]
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Intelephense.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
valid_platforms = [
|
||||
PlatformId.LINUX_x64,
|
||||
PlatformId.LINUX_arm64,
|
||||
PlatformId.OSX,
|
||||
PlatformId.OSX_x64,
|
||||
PlatformId.OSX_arm64,
|
||||
PlatformId.WIN_x64,
|
||||
PlatformId.WIN_arm64,
|
||||
]
|
||||
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy PHP at the moment"
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
runtime_dependencies = d.get("runtimeDependencies", [])
|
||||
intelephense_ls_dir = os.path.join(os.path.dirname(__file__), "static", "php-lsp")
|
||||
|
||||
# Verify both node and npm are installed
|
||||
is_node_installed = shutil.which('node') is not None
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which('npm') is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
|
||||
# Install intelephense if not already installed
|
||||
if not os.path.exists(intelephense_ls_dir):
|
||||
os.makedirs(intelephense_ls_dir, exist_ok=True)
|
||||
for dependency in runtime_dependencies:
|
||||
# Windows doesn't support the 'user' parameter and doesn't have pwd module
|
||||
if PlatformUtils.get_platform_id().value.startswith("win"):
|
||||
subprocess.run(
|
||||
dependency["command"],
|
||||
shell=True,
|
||||
check=True,
|
||||
cwd=intelephense_ls_dir,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
else:
|
||||
# On Unix-like systems, run as non-root user
|
||||
import pwd
|
||||
user = pwd.getpwuid(os.getuid()).pw_name
|
||||
subprocess.run(
|
||||
dependency["command"],
|
||||
shell=True,
|
||||
check=True,
|
||||
user=user,
|
||||
cwd=intelephense_ls_dir,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
intelephense_executable_path = os.path.join(intelephense_ls_dir, "node_modules", ".bin", "intelephense")
|
||||
assert os.path.exists(intelephense_executable_path), "intelephense executable not found. Please install intelephense and try again."
|
||||
|
||||
return f"{intelephense_executable_path} --stdio"
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
# Setup runtime dependencies before initializing
|
||||
intelephense_cmd = self.setup_runtime_dependencies(logger, config)
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=intelephense_cmd, cwd=repository_root_path),
|
||||
"php"
|
||||
)
|
||||
self.request_id = 0
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the TypeScript Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""Start Intelephense server process"""
|
||||
def register_capability_handler(params):
|
||||
return
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_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)
|
||||
|
||||
self.logger.log("Starting Intelephense server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
self.logger.log(
|
||||
"After sent initialize params",
|
||||
logging.INFO,
|
||||
)
|
||||
|
||||
# Verify server capabilities
|
||||
assert "textDocumentSync" in init_response["capabilities"]
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert "definitionProvider" in init_response["capabilities"]
|
||||
|
||||
self.server.notify.initialized({})
|
||||
self.completions_available.set()
|
||||
|
||||
# Intelephense server is typically ready immediately after initialization
|
||||
# TODO: This is probably incorrect; the server does send an initialized notification, which we could wait for!
|
||||
|
||||
@override
|
||||
# For some reason, the LS may need longer to process this, so we just retry
|
||||
def _send_references_request(self, relative_file_path: str, line: int, column: int):
|
||||
# TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is
|
||||
# despite the LS having processed requests already. I don't know what causes this, but sleeping
|
||||
# one second helps. It may be that sleeping only once is enough but that's hard to reliably test.
|
||||
# May be related to the time it takes to read the files or something like that.
|
||||
# The sleeping doesn't seem to be needed on all systems
|
||||
sleep(1)
|
||||
return super()._send_references_request(relative_file_path, line, column)
|
||||
|
||||
@override
|
||||
def _send_definition_request(self, definition_params: DefinitionParams):
|
||||
# TODO: same as above, also only a problem if the definition is in another file
|
||||
sleep(1)
|
||||
return super()._send_definition_request(definition_params)
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"_description": "Used to download the runtime dependencies for running intelephense. Obtained from https://www.npmjs.com/package/intelephense",
|
||||
"runtimeDependencies": [
|
||||
{
|
||||
"id": "intelephense",
|
||||
"description": "Intelephense package for Linux, OSX, and Windows. Both x64 and arm64 are supported.",
|
||||
"command": "npm install --prefix ./ intelephense@1.14.4"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,905 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"clientInfo": {
|
||||
"name": "Visual Studio Code - Insiders",
|
||||
"version": "1.81.0-insider"
|
||||
},
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": true,
|
||||
"workspaceEdit": {
|
||||
"documentChanges": true,
|
||||
"resourceOperations": [
|
||||
"create",
|
||||
"rename",
|
||||
"delete"
|
||||
],
|
||||
"failureHandling": "textOnlyTransactional",
|
||||
"normalizesLineEndings": true,
|
||||
"changeAnnotationSupport": {
|
||||
"groupsOnLabel": true
|
||||
}
|
||||
},
|
||||
"configuration": true,
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": true,
|
||||
"relativePatternSupport": true
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"location.range"
|
||||
]
|
||||
}
|
||||
},
|
||||
"codeLens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"workspaceFolders": true,
|
||||
"semanticTokens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"fileOperations": {
|
||||
"dynamicRegistration": true,
|
||||
"didCreate": true,
|
||||
"didRename": true,
|
||||
"didDelete": true,
|
||||
"willCreate": true,
|
||||
"willRename": true,
|
||||
"willDelete": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"refreshSupport": true
|
||||
}
|
||||
},
|
||||
"textDocument": {
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": true,
|
||||
"versionSupport": false,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"codeDescriptionSupport": true,
|
||||
"dataSupport": true
|
||||
},
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"willSave": true,
|
||||
"willSaveWaitUntil": true,
|
||||
"didSave": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"contextSupport": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true,
|
||||
"commitCharactersSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"deprecatedSupport": true,
|
||||
"preselectSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"insertReplaceSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"documentation",
|
||||
"detail",
|
||||
"additionalTextEdits"
|
||||
]
|
||||
},
|
||||
"insertTextModeSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"labelDetailsSupport": true
|
||||
},
|
||||
"insertTextMode": 2,
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"completionList": {
|
||||
"itemDefaults": [
|
||||
"commitCharacters",
|
||||
"editRange",
|
||||
"insertTextFormat",
|
||||
"insertTextMode"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": true,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"parameterInformation": {
|
||||
"labelOffsetSupport": true
|
||||
},
|
||||
"activeParameterSupport": true
|
||||
},
|
||||
"contextSupport": true
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"labelSupport": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"isPreferredSupport": true,
|
||||
"disabledSupport": true,
|
||||
"dataSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
},
|
||||
"honorsChangeAnnotations": false
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": true,
|
||||
"prepareSupport": true,
|
||||
"prepareSupportDefaultBehavior": 1,
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": true,
|
||||
"tooltipSupport": true
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"foldingRange": {
|
||||
"dynamicRegistration": true,
|
||||
"rangeLimit": 5000,
|
||||
"lineFoldingOnly": true,
|
||||
"foldingRangeKind": {
|
||||
"valueSet": [
|
||||
"comment",
|
||||
"imports",
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"foldingRange": {
|
||||
"collapsedText": false
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"selectionRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"callHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"dynamicRegistration": true,
|
||||
"tokenTypes": [
|
||||
"namespace",
|
||||
"type",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"struct",
|
||||
"typeParameter",
|
||||
"parameter",
|
||||
"variable",
|
||||
"property",
|
||||
"enumMember",
|
||||
"event",
|
||||
"function",
|
||||
"method",
|
||||
"macro",
|
||||
"keyword",
|
||||
"modifier",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"decorator"
|
||||
],
|
||||
"tokenModifiers": [
|
||||
"declaration",
|
||||
"definition",
|
||||
"readonly",
|
||||
"static",
|
||||
"deprecated",
|
||||
"abstract",
|
||||
"async",
|
||||
"modification",
|
||||
"documentation",
|
||||
"defaultLibrary"
|
||||
],
|
||||
"formats": [
|
||||
"relative"
|
||||
],
|
||||
"requests": {
|
||||
"range": true,
|
||||
"full": {
|
||||
"delta": true
|
||||
}
|
||||
},
|
||||
"multilineTokenSupport": false,
|
||||
"overlappingTokenSupport": false,
|
||||
"serverCancelSupport": true,
|
||||
"augmentsSyntaxTokens": false
|
||||
},
|
||||
"linkedEditingRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"dynamicRegistration": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"tooltip",
|
||||
"textEdits",
|
||||
"label.tooltip",
|
||||
"label.location",
|
||||
"label.command"
|
||||
]
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"dynamicRegistration": true,
|
||||
"relatedDocumentSupport": false
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"showMessage": {
|
||||
"messageActionItem": {
|
||||
"additionalPropertiesSupport": true
|
||||
}
|
||||
},
|
||||
"showDocument": {
|
||||
"support": true
|
||||
},
|
||||
"workDoneProgress": true
|
||||
},
|
||||
"general": {
|
||||
"staleRequestSupport": {
|
||||
"cancel": true,
|
||||
"retryOnContentModified": [
|
||||
"textDocument/semanticTokens/full",
|
||||
"textDocument/semanticTokens/range",
|
||||
"textDocument/semanticTokens/full/delta"
|
||||
]
|
||||
},
|
||||
"regularExpressions": {
|
||||
"engine": "ECMAScript",
|
||||
"version": "ES2020"
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "marked",
|
||||
"version": "1.1.0",
|
||||
"allowedTags": [
|
||||
"ul",
|
||||
"li",
|
||||
"p",
|
||||
"code",
|
||||
"blockquote",
|
||||
"ol",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"hr",
|
||||
"em",
|
||||
"pre",
|
||||
"table",
|
||||
"thead",
|
||||
"tbody",
|
||||
"tr",
|
||||
"th",
|
||||
"td",
|
||||
"div",
|
||||
"del",
|
||||
"a",
|
||||
"strong",
|
||||
"br",
|
||||
"img",
|
||||
"span"
|
||||
]
|
||||
},
|
||||
"positionEncodings": [
|
||||
"utf-16"
|
||||
]
|
||||
},
|
||||
"notebookDocument": {
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"executionSummarySupport": true
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"snippetTextEdit": true,
|
||||
"codeActionGroup": true,
|
||||
"hoverActions": true,
|
||||
"serverStatusNotification": true,
|
||||
"colorDiagnosticOutput": true,
|
||||
"openServerLogs": true,
|
||||
"commands": {
|
||||
"commands": [
|
||||
"rust-analyzer.runSingle",
|
||||
"rust-analyzer.debugSingle",
|
||||
"rust-analyzer.showReferences",
|
||||
"rust-analyzer.gotoLocation",
|
||||
"editor.action.triggerParameterHints"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"initializationOptions": {
|
||||
"cargoRunner": null,
|
||||
"runnables": {
|
||||
"extraEnv": null,
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"command": null,
|
||||
"extraArgs": []
|
||||
},
|
||||
"server": {
|
||||
"path": null,
|
||||
"extraEnv": null
|
||||
},
|
||||
"trace": {
|
||||
"server": "verbose",
|
||||
"extension": true
|
||||
},
|
||||
"debug": {
|
||||
"engine": "auto",
|
||||
"sourceFileMap": {
|
||||
"/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
|
||||
},
|
||||
"openDebugPane": false,
|
||||
"engineSettings": {}
|
||||
},
|
||||
"restartServerOnConfigChange": false,
|
||||
"typing": {
|
||||
"continueCommentsOnNewline": true,
|
||||
"autoClosingAngleBrackets": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"previewRustcOutput": false,
|
||||
"useRustcErrorCode": false,
|
||||
"disabled": [],
|
||||
"enable": true,
|
||||
"experimental": {
|
||||
"enable": false
|
||||
},
|
||||
"remapPrefix": {},
|
||||
"warningsAsHint": [],
|
||||
"warningsAsInfo": []
|
||||
},
|
||||
"discoverProjectCommand": null,
|
||||
"showUnlinkedFileNotification": true,
|
||||
"showDependenciesExplorer": true,
|
||||
"assist": {
|
||||
"emitMustUse": false,
|
||||
"expressionFillDefault": "todo"
|
||||
},
|
||||
"cachePriming": {
|
||||
"enable": true,
|
||||
"numThreads": 0
|
||||
},
|
||||
"cargo": {
|
||||
"autoreload": true,
|
||||
"buildScripts": {
|
||||
"enable": true,
|
||||
"invocationLocation": "workspace",
|
||||
"invocationStrategy": "per_workspace",
|
||||
"overrideCommand": null,
|
||||
"useRustcWrapper": true
|
||||
},
|
||||
"cfgs": {},
|
||||
"extraArgs": [],
|
||||
"extraEnv": {},
|
||||
"features": [],
|
||||
"noDefaultFeatures": false,
|
||||
"sysroot": "discover",
|
||||
"sysrootSrc": null,
|
||||
"target": null,
|
||||
"unsetTest": [
|
||||
"core"
|
||||
]
|
||||
},
|
||||
"checkOnSave": true,
|
||||
"check": {
|
||||
"allTargets": true,
|
||||
"command": "check",
|
||||
"extraArgs": [],
|
||||
"extraEnv": {},
|
||||
"features": null,
|
||||
"invocationLocation": "workspace",
|
||||
"invocationStrategy": "per_workspace",
|
||||
"noDefaultFeatures": null,
|
||||
"overrideCommand": null,
|
||||
"targets": null
|
||||
},
|
||||
"completion": {
|
||||
"autoimport": {
|
||||
"enable": true
|
||||
},
|
||||
"autoself": {
|
||||
"enable": true
|
||||
},
|
||||
"callable": {
|
||||
"snippets": "fill_arguments"
|
||||
},
|
||||
"limit": null,
|
||||
"postfix": {
|
||||
"enable": true
|
||||
},
|
||||
"privateEditable": {
|
||||
"enable": false
|
||||
},
|
||||
"snippets": {
|
||||
"custom": {
|
||||
"Arc::new": {
|
||||
"postfix": "arc",
|
||||
"body": "Arc::new(${receiver})",
|
||||
"requires": "std::sync::Arc",
|
||||
"description": "Put the expression into an `Arc`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Rc::new": {
|
||||
"postfix": "rc",
|
||||
"body": "Rc::new(${receiver})",
|
||||
"requires": "std::rc::Rc",
|
||||
"description": "Put the expression into an `Rc`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Box::pin": {
|
||||
"postfix": "pinbox",
|
||||
"body": "Box::pin(${receiver})",
|
||||
"requires": "std::boxed::Box",
|
||||
"description": "Put the expression into a pinned `Box`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Ok": {
|
||||
"postfix": "ok",
|
||||
"body": "Ok(${receiver})",
|
||||
"description": "Wrap the expression in a `Result::Ok`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Err": {
|
||||
"postfix": "err",
|
||||
"body": "Err(${receiver})",
|
||||
"description": "Wrap the expression in a `Result::Err`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Some": {
|
||||
"postfix": "some",
|
||||
"body": "Some(${receiver})",
|
||||
"description": "Wrap the expression in an `Option::Some`",
|
||||
"scope": "expr"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"excludeDirs": [],
|
||||
"watcher": "client"
|
||||
},
|
||||
"highlightRelated": {
|
||||
"breakPoints": {
|
||||
"enable": true
|
||||
},
|
||||
"closureCaptures": {
|
||||
"enable": true
|
||||
},
|
||||
"exitPoints": {
|
||||
"enable": true
|
||||
},
|
||||
"references": {
|
||||
"enable": true
|
||||
},
|
||||
"yieldPoints": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"actions": {
|
||||
"debug": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"gotoTypeDef": {
|
||||
"enable": true
|
||||
},
|
||||
"implementations": {
|
||||
"enable": true
|
||||
},
|
||||
"references": {
|
||||
"enable": false
|
||||
},
|
||||
"run": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"documentation": {
|
||||
"enable": true,
|
||||
"keywords": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"enable": true
|
||||
},
|
||||
"memoryLayout": {
|
||||
"alignment": "hexadecimal",
|
||||
"enable": true,
|
||||
"niches": false,
|
||||
"offset": "hexadecimal",
|
||||
"size": "both"
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"granularity": {
|
||||
"enforce": false,
|
||||
"group": "crate"
|
||||
},
|
||||
"group": {
|
||||
"enable": true
|
||||
},
|
||||
"merge": {
|
||||
"glob": true
|
||||
},
|
||||
"prefer": {
|
||||
"no": {
|
||||
"std": false
|
||||
}
|
||||
},
|
||||
"prefix": "plain"
|
||||
},
|
||||
"inlayHints": {
|
||||
"bindingModeHints": {
|
||||
"enable": false
|
||||
},
|
||||
"chainingHints": {
|
||||
"enable": true
|
||||
},
|
||||
"closingBraceHints": {
|
||||
"enable": true,
|
||||
"minLines": 25
|
||||
},
|
||||
"closureCaptureHints": {
|
||||
"enable": false
|
||||
},
|
||||
"closureReturnTypeHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"closureStyle": "impl_fn",
|
||||
"discriminantHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"expressionAdjustmentHints": {
|
||||
"enable": "never",
|
||||
"hideOutsideUnsafe": false,
|
||||
"mode": "prefix"
|
||||
},
|
||||
"lifetimeElisionHints": {
|
||||
"enable": "never",
|
||||
"useParameterNames": false
|
||||
},
|
||||
"maxLength": 25,
|
||||
"parameterHints": {
|
||||
"enable": true
|
||||
},
|
||||
"reborrowHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"renderColons": true,
|
||||
"typeHints": {
|
||||
"enable": true,
|
||||
"hideClosureInitialization": false,
|
||||
"hideNamedConstructor": false
|
||||
}
|
||||
},
|
||||
"interpret": {
|
||||
"tests": false
|
||||
},
|
||||
"joinLines": {
|
||||
"joinAssignments": true,
|
||||
"joinElseIf": true,
|
||||
"removeTrailingComma": true,
|
||||
"unwrapTrivialBlock": true
|
||||
},
|
||||
"lens": {
|
||||
"debug": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"forceCustomCommands": true,
|
||||
"implementations": {
|
||||
"enable": true
|
||||
},
|
||||
"location": "above_name",
|
||||
"references": {
|
||||
"adt": {
|
||||
"enable": false
|
||||
},
|
||||
"enumVariant": {
|
||||
"enable": false
|
||||
},
|
||||
"method": {
|
||||
"enable": false
|
||||
},
|
||||
"trait": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"run": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"linkedProjects": [],
|
||||
"lru": {
|
||||
"capacity": null,
|
||||
"query": {
|
||||
"capacities": {}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"cargoTomlNotFound": true
|
||||
},
|
||||
"numThreads": null,
|
||||
"procMacro": {
|
||||
"attributes": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"ignored": {},
|
||||
"server": null
|
||||
},
|
||||
"references": {
|
||||
"excludeImports": false
|
||||
},
|
||||
"rustc": {
|
||||
"source": null
|
||||
},
|
||||
"rustfmt": {
|
||||
"extraArgs": [],
|
||||
"overrideCommand": null,
|
||||
"rangeFormatting": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"semanticHighlighting": {
|
||||
"doc": {
|
||||
"comment": {
|
||||
"inject": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"nonStandardTokens": true,
|
||||
"operator": {
|
||||
"enable": true,
|
||||
"specialization": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"punctuation": {
|
||||
"enable": false,
|
||||
"separate": {
|
||||
"macro": {
|
||||
"bang": false
|
||||
}
|
||||
},
|
||||
"specialization": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"strings": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"signatureInfo": {
|
||||
"detail": "full",
|
||||
"documentation": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"symbols": {
|
||||
"maxSymbols": 0
|
||||
}
|
||||
}
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,107 @@
|
||||
"""
|
||||
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class JediServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a JediServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd="jedi-language-server", cwd=repository_root_path),
|
||||
"python",
|
||||
)
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"]
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Jedi Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the JEDI Language Server
|
||||
"""
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def check_experimental_status(params):
|
||||
if params["quiescent"] == True:
|
||||
self.completions_available.set()
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
self.server.on_request("client/registerCapability", do_nothing)
|
||||
self.server.on_notification("language/status", do_nothing)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
|
||||
self.logger.log("Starting jedi-language-server server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert init_response["capabilities"]["completionProvider"] == {
|
||||
"triggerCharacters": [".", "'", '"'],
|
||||
"resolveProvider": True,
|
||||
}
|
||||
|
||||
self.server.notify.initialized({})
|
||||
@@ -0,0 +1,521 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"clientInfo": {
|
||||
"name": "Multilspy Kotlin Client",
|
||||
"version": "1.0.0"
|
||||
},
|
||||
"locale": "en",
|
||||
"rootPath": "repository_absolute_path",
|
||||
"rootUri": "pathlib.Path(repository_absolute_path).as_uri()",
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": true,
|
||||
"workspaceEdit": {
|
||||
"documentChanges": true,
|
||||
"resourceOperations": [
|
||||
"create",
|
||||
"rename",
|
||||
"delete"
|
||||
],
|
||||
"failureHandling": "textOnlyTransactional",
|
||||
"normalizesLineEndings": true,
|
||||
"changeAnnotationSupport": {
|
||||
"groupsOnLabel": true
|
||||
}
|
||||
},
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": true,
|
||||
"relativePatternSupport": true
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"location.range"
|
||||
]
|
||||
}
|
||||
},
|
||||
"codeLens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"configuration": true,
|
||||
"workspaceFolders": true,
|
||||
"semanticTokens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"fileOperations": {
|
||||
"dynamicRegistration": true,
|
||||
"didCreate": true,
|
||||
"didRename": true,
|
||||
"didDelete": true,
|
||||
"willCreate": true,
|
||||
"willRename": true,
|
||||
"willDelete": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"refreshSupport": true
|
||||
}
|
||||
},
|
||||
"textDocument": {
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": true,
|
||||
"versionSupport": false,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"codeDescriptionSupport": true,
|
||||
"dataSupport": true
|
||||
},
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"willSave": true,
|
||||
"willSaveWaitUntil": true,
|
||||
"didSave": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"contextSupport": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": false,
|
||||
"commitCharactersSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"deprecatedSupport": true,
|
||||
"preselectSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"insertReplaceSupport": false,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"documentation",
|
||||
"detail",
|
||||
"additionalTextEdits"
|
||||
]
|
||||
},
|
||||
"insertTextModeSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"labelDetailsSupport": true
|
||||
},
|
||||
"insertTextMode": 2,
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"completionList": {
|
||||
"itemDefaults": [
|
||||
"commitCharacters",
|
||||
"editRange",
|
||||
"insertTextFormat",
|
||||
"insertTextMode"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": true,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"parameterInformation": {
|
||||
"labelOffsetSupport": true
|
||||
},
|
||||
"activeParameterSupport": true
|
||||
},
|
||||
"contextSupport": true
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"labelSupport": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"isPreferredSupport": true,
|
||||
"disabledSupport": true,
|
||||
"dataSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
},
|
||||
"honorsChangeAnnotations": false
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": true,
|
||||
"prepareSupport": true,
|
||||
"prepareSupportDefaultBehavior": 1,
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": true,
|
||||
"tooltipSupport": true
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"foldingRange": {
|
||||
"dynamicRegistration": true,
|
||||
"rangeLimit": 5000,
|
||||
"lineFoldingOnly": true,
|
||||
"foldingRangeKind": {
|
||||
"valueSet": [
|
||||
"comment",
|
||||
"imports",
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"foldingRange": {
|
||||
"collapsedText": false
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"selectionRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"callHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"dynamicRegistration": true,
|
||||
"tokenTypes": [
|
||||
"namespace",
|
||||
"type",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"struct",
|
||||
"typeParameter",
|
||||
"parameter",
|
||||
"variable",
|
||||
"property",
|
||||
"enumMember",
|
||||
"event",
|
||||
"function",
|
||||
"method",
|
||||
"macro",
|
||||
"keyword",
|
||||
"modifier",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"decorator"
|
||||
],
|
||||
"tokenModifiers": [
|
||||
"declaration",
|
||||
"definition",
|
||||
"readonly",
|
||||
"static",
|
||||
"deprecated",
|
||||
"abstract",
|
||||
"async",
|
||||
"modification",
|
||||
"documentation",
|
||||
"defaultLibrary"
|
||||
],
|
||||
"formats": [
|
||||
"relative"
|
||||
],
|
||||
"requests": {
|
||||
"range": true,
|
||||
"full": {
|
||||
"delta": true
|
||||
}
|
||||
},
|
||||
"multilineTokenSupport": false,
|
||||
"overlappingTokenSupport": false,
|
||||
"serverCancelSupport": true,
|
||||
"augmentsSyntaxTokens": true
|
||||
},
|
||||
"linkedEditingRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"dynamicRegistration": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"tooltip",
|
||||
"textEdits",
|
||||
"label.tooltip",
|
||||
"label.location",
|
||||
"label.command"
|
||||
]
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"dynamicRegistration": true,
|
||||
"relatedDocumentSupport": false
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"showMessage": {
|
||||
"messageActionItem": {
|
||||
"additionalPropertiesSupport": true
|
||||
}
|
||||
},
|
||||
"showDocument": {
|
||||
"support": true
|
||||
},
|
||||
"workDoneProgress": true
|
||||
},
|
||||
"general": {
|
||||
"staleRequestSupport": {
|
||||
"cancel": true,
|
||||
"retryOnContentModified": [
|
||||
"textDocument/semanticTokens/full",
|
||||
"textDocument/semanticTokens/range",
|
||||
"textDocument/semanticTokens/full/delta"
|
||||
]
|
||||
},
|
||||
"regularExpressions": {
|
||||
"engine": "ECMAScript",
|
||||
"version": "ES2020"
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "marked",
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"positionEncodings": [
|
||||
"utf-16"
|
||||
]
|
||||
},
|
||||
"notebookDocument": {
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"executionSummarySupport": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"initializationOptions": {
|
||||
"workspaceFolders": "[pathlib.Path(repository_absolute_path).as_uri()]",
|
||||
"storagePath": null,
|
||||
"codegen": {
|
||||
"enabled": false
|
||||
},
|
||||
"compiler": {
|
||||
"jvm": {
|
||||
"target": "default"
|
||||
}
|
||||
},
|
||||
"completion": {
|
||||
"snippets": {
|
||||
"enabled": true
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"enabled": true,
|
||||
"level": 4,
|
||||
"debounceTime": 250
|
||||
},
|
||||
"scripts": {
|
||||
"enabled": true,
|
||||
"buildScriptsEnabled": true
|
||||
},
|
||||
"indexing": {
|
||||
"enabled": true
|
||||
},
|
||||
"externalSources": {
|
||||
"useKlsScheme": false,
|
||||
"autoConvertToKotlin": false
|
||||
},
|
||||
"inlayHints": {
|
||||
"typeHints": false,
|
||||
"parameterHints": false,
|
||||
"chainedHints": false
|
||||
},
|
||||
"formatting": {
|
||||
"formatter": "ktfmt",
|
||||
"ktfmt": {
|
||||
"style": "google",
|
||||
"indent": 4,
|
||||
"maxWidth": 100,
|
||||
"continuationIndent": 8,
|
||||
"removeUnusedImports": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": "[\n {\n \"uri\": pathlib.Path(repository_absolute_path).as_uri(),\n \"name\": os.path.basename(repository_absolute_path),\n }\n ]"
|
||||
}
|
||||
@@ -0,0 +1,210 @@
|
||||
"""
|
||||
Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin.
|
||||
"""
|
||||
|
||||
import dataclasses
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_utils import FileUtils
|
||||
from multilspy.multilspy_utils import PlatformUtils
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class KotlinRuntimeDependencyPaths:
|
||||
"""
|
||||
Stores the paths to the runtime dependencies of Kotlin Language Server
|
||||
"""
|
||||
java_path: str
|
||||
java_home_path: str
|
||||
kotlin_executable_path: str
|
||||
|
||||
|
||||
class KotlinLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Kotlin specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Kotlin.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a Kotlin Language Server instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
runtime_dependency_paths = self.setup_runtime_dependencies(logger, config)
|
||||
self.runtime_dependency_paths = runtime_dependency_paths
|
||||
|
||||
# Create command to execute the Kotlin Language Server script
|
||||
cmd = f'"{self.runtime_dependency_paths.kotlin_executable_path}"'
|
||||
|
||||
# Set environment variables including JAVA_HOME
|
||||
proc_env = {"JAVA_HOME": self.runtime_dependency_paths.java_home_path}
|
||||
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=cmd, env=proc_env, cwd=repository_root_path),
|
||||
"kotlin",
|
||||
)
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> KotlinRuntimeDependencyPaths:
|
||||
"""
|
||||
Setup runtime dependencies for Kotlin Language Server.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
# Verify platform support
|
||||
assert platform_id.value.startswith("win-") or platform_id.value.startswith("linux-") or platform_id.value.startswith("osx-"), "Only Windows, Linux and macOS platforms are supported for Kotlin in multilspy at the moment"
|
||||
|
||||
# Load dependency information
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
kotlin_dependency = d["runtimeDependency"]
|
||||
java_dependency = d["java"][platform_id.value]
|
||||
|
||||
# Setup paths for dependencies
|
||||
static_dir = os.path.join(os.path.dirname(__file__), "static")
|
||||
os.makedirs(static_dir, exist_ok=True)
|
||||
|
||||
# Setup Java paths
|
||||
java_dir = os.path.join(static_dir, "java")
|
||||
os.makedirs(java_dir, exist_ok=True)
|
||||
|
||||
java_home_path = os.path.join(java_dir, java_dependency["java_home_path"])
|
||||
java_path = os.path.join(java_dir, java_dependency["java_path"])
|
||||
|
||||
# Download and extract Java if not exists
|
||||
if not os.path.exists(java_path):
|
||||
logger.log(f"Downloading Java for {platform_id.value}...", logging.INFO)
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, java_dependency["url"], java_dir, java_dependency["archiveType"]
|
||||
)
|
||||
# Make Java executable
|
||||
if not platform_id.value.startswith("win-"):
|
||||
os.chmod(java_path, 0o755)
|
||||
|
||||
assert os.path.exists(java_path), f"Java executable not found at {java_path}"
|
||||
|
||||
# Setup Kotlin Language Server paths
|
||||
kotlin_ls_dir = os.path.join(static_dir, "server")
|
||||
|
||||
# Get platform-specific executable script path
|
||||
if platform_id.value.startswith("win-"):
|
||||
kotlin_script = os.path.join(kotlin_ls_dir, "bin", "kotlin-language-server.bat")
|
||||
else:
|
||||
kotlin_script = os.path.join(kotlin_ls_dir, "bin", "kotlin-language-server")
|
||||
|
||||
# Download and extract Kotlin Language Server if script doesn't exist
|
||||
if not os.path.exists(kotlin_script):
|
||||
logger.log("Downloading Kotlin Language Server...", logging.INFO)
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, kotlin_dependency["url"], static_dir, kotlin_dependency["archiveType"]
|
||||
)
|
||||
|
||||
# Make script executable on Unix platforms
|
||||
if os.path.exists(kotlin_script) and not platform_id.value.startswith("win-"):
|
||||
os.chmod(kotlin_script, stat.S_IRUSR | stat.S_IWUSR | stat.S_IXUSR | stat.S_IRGRP | stat.S_IXGRP | stat.S_IROTH | stat.S_IXOTH)
|
||||
|
||||
# Use script file
|
||||
if os.path.exists(kotlin_script):
|
||||
kotlin_executable_path = kotlin_script
|
||||
logger.log(f"Using Kotlin Language Server script at {kotlin_script}", logging.INFO)
|
||||
else:
|
||||
raise FileNotFoundError(f"Kotlin Language Server script not found at {kotlin_script}")
|
||||
|
||||
return KotlinRuntimeDependencyPaths(
|
||||
java_path=java_path,
|
||||
java_home_path=java_home_path,
|
||||
kotlin_executable_path=kotlin_executable_path
|
||||
)
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Kotlin Language Server.
|
||||
"""
|
||||
with open(str(pathlib.PurePath(os.path.dirname(__file__), "initialize_params.json")), "r", encoding="utf-8") as f:
|
||||
d: InitializeParams = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
if not os.path.isabs(repository_absolute_path):
|
||||
repository_absolute_path = os.path.abspath(repository_absolute_path)
|
||||
|
||||
assert d["processId"] == "os.getpid()"
|
||||
d["processId"] = os.getpid()
|
||||
|
||||
assert d["rootPath"] == "repository_absolute_path"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "pathlib.Path(repository_absolute_path).as_uri()"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["initializationOptions"]["workspaceFolders"] == "[pathlib.Path(repository_absolute_path).as_uri()]"
|
||||
d["initializationOptions"]["workspaceFolders"] = [pathlib.Path(repository_absolute_path).as_uri()]
|
||||
|
||||
assert (
|
||||
d["workspaceFolders"]
|
||||
== '[\n {\n "uri": pathlib.Path(repository_absolute_path).as_uri(),\n "name": os.path.basename(repository_absolute_path),\n }\n ]'
|
||||
)
|
||||
d["workspaceFolders"] = [
|
||||
{
|
||||
"uri": pathlib.Path(repository_absolute_path).as_uri(),
|
||||
"name": os.path.basename(repository_absolute_path),
|
||||
}
|
||||
]
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the Kotlin Language Server
|
||||
"""
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
self.server.on_request("client/registerCapability", do_nothing)
|
||||
self.server.on_notification("language/status", do_nothing)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
|
||||
self.logger.log("Starting Kotlin server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
capabilities = init_response["capabilities"]
|
||||
assert "textDocumentSync" in capabilities, "Server must support textDocumentSync"
|
||||
assert "hoverProvider" in capabilities, "Server must support hover"
|
||||
assert "completionProvider" in capabilities, "Server must support code completion"
|
||||
assert "signatureHelpProvider" in capabilities, "Server must support signature help"
|
||||
assert "definitionProvider" in capabilities, "Server must support go to definition"
|
||||
assert "referencesProvider" in capabilities, "Server must support find references"
|
||||
assert "documentSymbolProvider" in capabilities, "Server must support document symbols"
|
||||
assert "workspaceSymbolProvider" in capabilities, "Server must support workspace symbols"
|
||||
assert "semanticTokensProvider" in capabilities, "Server must support semantic tokens"
|
||||
|
||||
self.server.notify.initialized({})
|
||||
self.completions_available.set()
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"_description": "Used to download the runtime dependencies for Kotlin Language Server from https://github.com/fwcd/kotlin-language-server",
|
||||
"runtimeDependency": {
|
||||
"id": "KotlinLsp",
|
||||
"description": "Kotlin Language Server",
|
||||
"url": "https://github.com/fwcd/kotlin-language-server/releases/download/1.3.13/server.zip",
|
||||
"archiveType": "zip"
|
||||
},
|
||||
"java": {
|
||||
"win-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-win32-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-win32-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-win32-x86_64/bin/java.exe"
|
||||
},
|
||||
"linux-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-linux-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-linux-x86_64/bin/java"
|
||||
},
|
||||
"linux-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-linux-arm64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-linux-aarch64",
|
||||
"java_path": "extension/jre/21.0.7-linux-aarch64/bin/java"
|
||||
},
|
||||
"osx-x64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-x64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-macosx-x86_64",
|
||||
"java_path": "extension/jre/21.0.7-macosx-x86_64/bin/java"
|
||||
},
|
||||
"osx-arm64": {
|
||||
"url": "https://github.com/redhat-developer/vscode-java/releases/download/v1.42.0/java-darwin-arm64-1.42.0-561.vsix",
|
||||
"archiveType": "zip",
|
||||
"java_home_path": "extension/jre/21.0.7-macosx-aarch64",
|
||||
"java_path": "extension/jre/21.0.7-macosx-aarch64/bin/java"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,631 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"clientInfo": {
|
||||
"name": "Visual Studio Code - Insiders",
|
||||
"version": "1.82.0-insider"
|
||||
},
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": true,
|
||||
"workspaceEdit": {
|
||||
"documentChanges": true,
|
||||
"resourceOperations": [
|
||||
"create",
|
||||
"rename",
|
||||
"delete"
|
||||
],
|
||||
"failureHandling": "textOnlyTransactional",
|
||||
"normalizesLineEndings": true,
|
||||
"changeAnnotationSupport": {
|
||||
"groupsOnLabel": true
|
||||
}
|
||||
},
|
||||
"configuration": false,
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": true,
|
||||
"relativePatternSupport": true
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"location.range"
|
||||
]
|
||||
}
|
||||
},
|
||||
"codeLens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"workspaceFolders": true,
|
||||
"semanticTokens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"fileOperations": {
|
||||
"dynamicRegistration": true,
|
||||
"didCreate": true,
|
||||
"didRename": true,
|
||||
"didDelete": true,
|
||||
"willCreate": true,
|
||||
"willRename": true,
|
||||
"willDelete": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"refreshSupport": true
|
||||
}
|
||||
},
|
||||
"textDocument": {
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": true,
|
||||
"versionSupport": false,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"codeDescriptionSupport": true,
|
||||
"dataSupport": true
|
||||
},
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"willSave": true,
|
||||
"willSaveWaitUntil": true,
|
||||
"didSave": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"contextSupport": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true,
|
||||
"commitCharactersSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"deprecatedSupport": true,
|
||||
"preselectSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"insertReplaceSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"documentation",
|
||||
"detail",
|
||||
"additionalTextEdits"
|
||||
]
|
||||
},
|
||||
"insertTextModeSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"labelDetailsSupport": true
|
||||
},
|
||||
"insertTextMode": 2,
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"completionList": {
|
||||
"itemDefaults": [
|
||||
"commitCharacters",
|
||||
"editRange",
|
||||
"insertTextFormat",
|
||||
"insertTextMode"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": true,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"parameterInformation": {
|
||||
"labelOffsetSupport": true
|
||||
},
|
||||
"activeParameterSupport": true
|
||||
},
|
||||
"contextSupport": true
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"labelSupport": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"isPreferredSupport": true,
|
||||
"disabledSupport": true,
|
||||
"dataSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
},
|
||||
"honorsChangeAnnotations": false
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": true,
|
||||
"prepareSupport": true,
|
||||
"prepareSupportDefaultBehavior": 1,
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": true,
|
||||
"tooltipSupport": true
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"foldingRange": {
|
||||
"dynamicRegistration": true,
|
||||
"rangeLimit": 5000,
|
||||
"lineFoldingOnly": true,
|
||||
"foldingRangeKind": {
|
||||
"valueSet": [
|
||||
"comment",
|
||||
"imports",
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"foldingRange": {
|
||||
"collapsedText": false
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"selectionRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"callHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"dynamicRegistration": true,
|
||||
"tokenTypes": [
|
||||
"namespace",
|
||||
"type",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"struct",
|
||||
"typeParameter",
|
||||
"parameter",
|
||||
"variable",
|
||||
"property",
|
||||
"enumMember",
|
||||
"event",
|
||||
"function",
|
||||
"method",
|
||||
"macro",
|
||||
"keyword",
|
||||
"modifier",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"decorator"
|
||||
],
|
||||
"tokenModifiers": [
|
||||
"declaration",
|
||||
"definition",
|
||||
"readonly",
|
||||
"static",
|
||||
"deprecated",
|
||||
"abstract",
|
||||
"async",
|
||||
"modification",
|
||||
"documentation",
|
||||
"defaultLibrary"
|
||||
],
|
||||
"formats": [
|
||||
"relative"
|
||||
],
|
||||
"requests": {
|
||||
"range": true,
|
||||
"full": {
|
||||
"delta": true
|
||||
}
|
||||
},
|
||||
"multilineTokenSupport": false,
|
||||
"overlappingTokenSupport": false,
|
||||
"serverCancelSupport": true,
|
||||
"augmentsSyntaxTokens": false
|
||||
},
|
||||
"linkedEditingRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"dynamicRegistration": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"tooltip",
|
||||
"textEdits",
|
||||
"label.tooltip",
|
||||
"label.location",
|
||||
"label.command"
|
||||
]
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"dynamicRegistration": true,
|
||||
"relatedDocumentSupport": false
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"showMessage": {
|
||||
"messageActionItem": {
|
||||
"additionalPropertiesSupport": true
|
||||
}
|
||||
},
|
||||
"showDocument": {
|
||||
"support": true
|
||||
},
|
||||
"workDoneProgress": true
|
||||
},
|
||||
"general": {
|
||||
"staleRequestSupport": {
|
||||
"cancel": true,
|
||||
"retryOnContentModified": [
|
||||
"textDocument/semanticTokens/full",
|
||||
"textDocument/semanticTokens/range",
|
||||
"textDocument/semanticTokens/full/delta"
|
||||
]
|
||||
},
|
||||
"regularExpressions": {
|
||||
"engine": "ECMAScript",
|
||||
"version": "ES2020"
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "marked",
|
||||
"version": "1.1.0",
|
||||
"allowedTags": [
|
||||
"ul",
|
||||
"li",
|
||||
"p",
|
||||
"code",
|
||||
"blockquote",
|
||||
"ol",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"hr",
|
||||
"em",
|
||||
"pre",
|
||||
"table",
|
||||
"thead",
|
||||
"tbody",
|
||||
"tr",
|
||||
"th",
|
||||
"td",
|
||||
"div",
|
||||
"del",
|
||||
"a",
|
||||
"strong",
|
||||
"br",
|
||||
"img",
|
||||
"span"
|
||||
]
|
||||
},
|
||||
"positionEncodings": [
|
||||
"utf-16"
|
||||
]
|
||||
},
|
||||
"notebookDocument": {
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"executionSummarySupport": true
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"snippetTextEdit": true,
|
||||
"codeActionGroup": true,
|
||||
"hoverActions": true,
|
||||
"serverStatusNotification": true,
|
||||
"colorDiagnosticOutput": true,
|
||||
"openServerLogs": true,
|
||||
"commands": {
|
||||
"commands": [
|
||||
"editor.action.triggerParameterHints"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"initializationOptions": {
|
||||
"RoslynExtensionsOptions": {
|
||||
"EnableDecompilationSupport": false,
|
||||
"EnableAnalyzersSupport": true,
|
||||
"EnableImportCompletion": true,
|
||||
"EnableAsyncCompletion": false,
|
||||
"DocumentAnalysisTimeoutMs": 30000,
|
||||
"DiagnosticWorkersThreadCount": 18,
|
||||
"AnalyzeOpenDocumentsOnly": true,
|
||||
"InlayHintsOptions": {
|
||||
"EnableForParameters": false,
|
||||
"ForLiteralParameters": false,
|
||||
"ForIndexerParameters": false,
|
||||
"ForObjectCreationParameters": false,
|
||||
"ForOtherParameters": false,
|
||||
"SuppressForParametersThatDifferOnlyBySuffix": false,
|
||||
"SuppressForParametersThatMatchMethodIntent": false,
|
||||
"SuppressForParametersThatMatchArgumentName": false,
|
||||
"EnableForTypes": false,
|
||||
"ForImplicitVariableTypes": false,
|
||||
"ForLambdaParameterTypes": false,
|
||||
"ForImplicitObjectCreation": false
|
||||
},
|
||||
"LocationPaths": null
|
||||
},
|
||||
"FormattingOptions": {
|
||||
"OrganizeImports": false,
|
||||
"EnableEditorConfigSupport": true,
|
||||
"NewLine": "\n",
|
||||
"UseTabs": false,
|
||||
"TabSize": 4,
|
||||
"IndentationSize": 4,
|
||||
"SpacingAfterMethodDeclarationName": false,
|
||||
"SeparateImportDirectiveGroups": false,
|
||||
"SpaceWithinMethodDeclarationParenthesis": false,
|
||||
"SpaceBetweenEmptyMethodDeclarationParentheses": false,
|
||||
"SpaceAfterMethodCallName": false,
|
||||
"SpaceWithinMethodCallParentheses": false,
|
||||
"SpaceBetweenEmptyMethodCallParentheses": false,
|
||||
"SpaceAfterControlFlowStatementKeyword": true,
|
||||
"SpaceWithinExpressionParentheses": false,
|
||||
"SpaceWithinCastParentheses": false,
|
||||
"SpaceWithinOtherParentheses": false,
|
||||
"SpaceAfterCast": false,
|
||||
"SpaceBeforeOpenSquareBracket": false,
|
||||
"SpaceBetweenEmptySquareBrackets": false,
|
||||
"SpaceWithinSquareBrackets": false,
|
||||
"SpaceAfterColonInBaseTypeDeclaration": true,
|
||||
"SpaceAfterComma": true,
|
||||
"SpaceAfterDot": false,
|
||||
"SpaceAfterSemicolonsInForStatement": true,
|
||||
"SpaceBeforeColonInBaseTypeDeclaration": true,
|
||||
"SpaceBeforeComma": false,
|
||||
"SpaceBeforeDot": false,
|
||||
"SpaceBeforeSemicolonsInForStatement": false,
|
||||
"SpacingAroundBinaryOperator": "single",
|
||||
"IndentBraces": false,
|
||||
"IndentBlock": true,
|
||||
"IndentSwitchSection": true,
|
||||
"IndentSwitchCaseSection": true,
|
||||
"IndentSwitchCaseSectionWhenBlock": true,
|
||||
"LabelPositioning": "oneLess",
|
||||
"WrappingPreserveSingleLine": true,
|
||||
"WrappingKeepStatementsOnSingleLine": true,
|
||||
"NewLinesForBracesInTypes": true,
|
||||
"NewLinesForBracesInMethods": true,
|
||||
"NewLinesForBracesInProperties": true,
|
||||
"NewLinesForBracesInAccessors": true,
|
||||
"NewLinesForBracesInAnonymousMethods": true,
|
||||
"NewLinesForBracesInControlBlocks": true,
|
||||
"NewLinesForBracesInAnonymousTypes": true,
|
||||
"NewLinesForBracesInObjectCollectionArrayInitializers": true,
|
||||
"NewLinesForBracesInLambdaExpressionBody": true,
|
||||
"NewLineForElse": true,
|
||||
"NewLineForCatch": true,
|
||||
"NewLineForFinally": true,
|
||||
"NewLineForMembersInObjectInit": true,
|
||||
"NewLineForMembersInAnonymousTypes": true,
|
||||
"NewLineForClausesInQuery": true
|
||||
},
|
||||
"FileOptions": {
|
||||
"SystemExcludeSearchPatterns": [
|
||||
"**/node_modules/**/*",
|
||||
"**/bin/**/*",
|
||||
"**/obj/**/*",
|
||||
"**/.git/**/*",
|
||||
"**/.git",
|
||||
"**/.svn",
|
||||
"**/.hg",
|
||||
"**/CVS",
|
||||
"**/.DS_Store",
|
||||
"**/Thumbs.db"
|
||||
],
|
||||
"ExcludeSearchPatterns": []
|
||||
},
|
||||
"RenameOptions": {
|
||||
"RenameOverloads": false,
|
||||
"RenameInStrings": false,
|
||||
"RenameInComments": false
|
||||
},
|
||||
"ImplementTypeOptions": {
|
||||
"InsertionBehavior": 0,
|
||||
"PropertyGenerationBehavior": 0
|
||||
},
|
||||
"DotNetCliOptions": {
|
||||
"LocationPaths": null
|
||||
},
|
||||
"Plugins": {
|
||||
"LocationPaths": null
|
||||
}
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,393 @@
|
||||
"""
|
||||
Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
from typing import Iterable
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_exceptions import MultilspyException
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_utils import FileUtils, PlatformUtils, PlatformId, DotnetVersion
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
def breadth_first_file_scan(root) -> Iterable[str]:
|
||||
"""
|
||||
This function was obtained from https://stackoverflow.com/questions/49654234/is-there-a-breadth-first-search-option-available-in-os-walk-or-equivalent-py
|
||||
It traverses the directory tree in breadth first order.
|
||||
"""
|
||||
dirs = [root]
|
||||
# while we has dirs to scan
|
||||
while len(dirs):
|
||||
next_dirs = []
|
||||
for parent in dirs:
|
||||
# scan each dir
|
||||
for f in os.listdir(parent):
|
||||
# if there is a dir, then save for next ittr
|
||||
# if it is a file then yield it (we'll return later)
|
||||
ff = os.path.join(parent, f)
|
||||
if os.path.isdir(ff):
|
||||
next_dirs.append(ff)
|
||||
else:
|
||||
yield ff
|
||||
|
||||
# once we've done all the current dirs then
|
||||
# we set up the next itter as the child dirs
|
||||
# from the current itter.
|
||||
dirs = next_dirs
|
||||
|
||||
|
||||
def find_least_depth_sln_file(root_dir) -> str | None:
|
||||
for filename in breadth_first_file_scan(root_dir):
|
||||
if filename.endswith(".sln"):
|
||||
return filename
|
||||
return None
|
||||
|
||||
|
||||
class OmniSharp(SolidLanguageServer):
|
||||
"""
|
||||
Provides C# specific instantiation of the LanguageServer class. Contains various configurations and settings specific to C#.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates an OmniSharp instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
omnisharp_executable_path, dll_path = self.setupRuntimeDependencies(logger, config)
|
||||
|
||||
slnfilename = find_least_depth_sln_file(repository_root_path)
|
||||
if slnfilename is None:
|
||||
logger.log("No *.sln file found in repository", logging.ERROR)
|
||||
raise MultilspyException("No SLN file found in repository")
|
||||
|
||||
cmd = " ".join(
|
||||
[
|
||||
omnisharp_executable_path,
|
||||
"-lsp",
|
||||
"--encoding",
|
||||
"ascii",
|
||||
"-z",
|
||||
"-s",
|
||||
f'"{slnfilename}"',
|
||||
"--hostPID",
|
||||
str(os.getpid()),
|
||||
"DotNet:enablePackageRestore=false",
|
||||
"--loglevel",
|
||||
"trace",
|
||||
"--plugin",
|
||||
dll_path,
|
||||
"FileOptions:SystemExcludeSearchPatterns:0=**/.git",
|
||||
"FileOptions:SystemExcludeSearchPatterns:1=**/.svn",
|
||||
"FileOptions:SystemExcludeSearchPatterns:2=**/.hg",
|
||||
"FileOptions:SystemExcludeSearchPatterns:3=**/CVS",
|
||||
"FileOptions:SystemExcludeSearchPatterns:4=**/.DS_Store",
|
||||
"FileOptions:SystemExcludeSearchPatterns:5=**/Thumbs.db",
|
||||
"RoslynExtensionsOptions:EnableAnalyzersSupport=true",
|
||||
"FormattingOptions:EnableEditorConfigSupport=true",
|
||||
"RoslynExtensionsOptions:EnableImportCompletion=true",
|
||||
"Sdk:IncludePrereleases=true",
|
||||
"RoslynExtensionsOptions:AnalyzeOpenDocumentsOnly=true",
|
||||
"formattingOptions:useTabs=false",
|
||||
"formattingOptions:tabSize=4",
|
||||
"formattingOptions:indentationSize=4",
|
||||
]
|
||||
)
|
||||
super().__init__(
|
||||
config, logger, repository_root_path, ProcessLaunchInfo(cmd=cmd, cwd=repository_root_path), "csharp"
|
||||
)
|
||||
|
||||
self.server_ready = threading.Event()
|
||||
self.definition_available = threading.Event()
|
||||
self.references_available = threading.Event()
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["bin", "obj"]
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Omnisharp Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def setupRuntimeDependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> tuple[str, str]:
|
||||
"""
|
||||
Setup runtime dependencies for OmniSharp.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
dotnet_version = PlatformUtils.get_dotnet_version()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
assert platform_id in [
|
||||
PlatformId.LINUX_x64,
|
||||
PlatformId.WIN_x64,
|
||||
], "Only linux-x64 and win-x64 platform is supported for in multilspy at the moment"
|
||||
assert dotnet_version in [
|
||||
DotnetVersion.V6,
|
||||
DotnetVersion.V7,
|
||||
DotnetVersion.V8
|
||||
], "Only dotnet version 6 and 7 are supported in multilspy at the moment"
|
||||
|
||||
# TODO: Do away with this assumption
|
||||
# Currently, runtime binaries are not available for .Net 7 and .Net 8. Hence, we assume .Net 6 runtime binaries to be compatible with .Net 7, .Net 8
|
||||
if dotnet_version in [DotnetVersion.V7, DotnetVersion.V8]:
|
||||
dotnet_version = DotnetVersion.V6
|
||||
|
||||
runtime_dependencies = d["runtimeDependencies"]
|
||||
runtime_dependencies = [
|
||||
dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value
|
||||
]
|
||||
runtime_dependencies = [
|
||||
dependency
|
||||
for dependency in runtime_dependencies
|
||||
if not ("dotnet_version" in dependency) or dependency["dotnet_version"] == dotnet_version.value
|
||||
]
|
||||
assert len(runtime_dependencies) == 2
|
||||
runtime_dependencies = {
|
||||
runtime_dependencies[0]["id"]: runtime_dependencies[0],
|
||||
runtime_dependencies[1]["id"]: runtime_dependencies[1],
|
||||
}
|
||||
|
||||
assert "OmniSharp" in runtime_dependencies
|
||||
assert "RazorOmnisharp" in runtime_dependencies
|
||||
|
||||
omnisharp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "OmniSharp")
|
||||
if not os.path.exists(omnisharp_ls_dir):
|
||||
os.makedirs(omnisharp_ls_dir)
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, runtime_dependencies["OmniSharp"]["url"], omnisharp_ls_dir, "zip"
|
||||
)
|
||||
omnisharp_executable_path = os.path.join(omnisharp_ls_dir, runtime_dependencies["OmniSharp"]["binaryName"])
|
||||
assert os.path.exists(omnisharp_executable_path)
|
||||
os.chmod(omnisharp_executable_path, stat.S_IEXEC)
|
||||
|
||||
razor_omnisharp_ls_dir = os.path.join(os.path.dirname(__file__), "static", "RazorOmnisharp")
|
||||
if not os.path.exists(razor_omnisharp_ls_dir):
|
||||
os.makedirs(razor_omnisharp_ls_dir)
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, runtime_dependencies["RazorOmnisharp"]["url"], razor_omnisharp_ls_dir, "zip"
|
||||
)
|
||||
razor_omnisharp_dll_path = os.path.join(
|
||||
razor_omnisharp_ls_dir, runtime_dependencies["RazorOmnisharp"]["dll_path"]
|
||||
)
|
||||
assert os.path.exists(razor_omnisharp_dll_path)
|
||||
|
||||
return omnisharp_executable_path, razor_omnisharp_dll_path
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the Omnisharp Language Server
|
||||
"""
|
||||
|
||||
def register_capability_handler(params):
|
||||
assert "registrations" in params
|
||||
for registration in params["registrations"]:
|
||||
if registration["method"] == "textDocument/definition":
|
||||
self.definition_available.set()
|
||||
if registration["method"] == "textDocument/references":
|
||||
self.references_available.set()
|
||||
if registration["method"] == "textDocument/completion":
|
||||
self.completions_available.set()
|
||||
|
||||
def lang_status_handler(params):
|
||||
# TODO: Should we wait for
|
||||
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
|
||||
# Before proceeding?
|
||||
# if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
|
||||
# self.service_ready_event.set()
|
||||
pass
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def check_experimental_status(params):
|
||||
if params["quiescent"] == True:
|
||||
self.server_ready.set()
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
def workspace_configuration_handler(params):
|
||||
# TODO: We do not know the appropriate way to handle this request. Should ideally contact the OmniSharp dev team
|
||||
return [
|
||||
{
|
||||
"RoslynExtensionsOptions": {
|
||||
"EnableDecompilationSupport": False,
|
||||
"EnableAnalyzersSupport": True,
|
||||
"EnableImportCompletion": True,
|
||||
"EnableAsyncCompletion": False,
|
||||
"DocumentAnalysisTimeoutMs": 30000,
|
||||
"DiagnosticWorkersThreadCount": 18,
|
||||
"AnalyzeOpenDocumentsOnly": True,
|
||||
"InlayHintsOptions": {
|
||||
"EnableForParameters": False,
|
||||
"ForLiteralParameters": False,
|
||||
"ForIndexerParameters": False,
|
||||
"ForObjectCreationParameters": False,
|
||||
"ForOtherParameters": False,
|
||||
"SuppressForParametersThatDifferOnlyBySuffix": False,
|
||||
"SuppressForParametersThatMatchMethodIntent": False,
|
||||
"SuppressForParametersThatMatchArgumentName": False,
|
||||
"EnableForTypes": False,
|
||||
"ForImplicitVariableTypes": False,
|
||||
"ForLambdaParameterTypes": False,
|
||||
"ForImplicitObjectCreation": False,
|
||||
},
|
||||
"LocationPaths": None,
|
||||
},
|
||||
"FormattingOptions": {
|
||||
"OrganizeImports": False,
|
||||
"EnableEditorConfigSupport": True,
|
||||
"NewLine": "\n",
|
||||
"UseTabs": False,
|
||||
"TabSize": 4,
|
||||
"IndentationSize": 4,
|
||||
"SpacingAfterMethodDeclarationName": False,
|
||||
"SeparateImportDirectiveGroups": False,
|
||||
"SpaceWithinMethodDeclarationParenthesis": False,
|
||||
"SpaceBetweenEmptyMethodDeclarationParentheses": False,
|
||||
"SpaceAfterMethodCallName": False,
|
||||
"SpaceWithinMethodCallParentheses": False,
|
||||
"SpaceBetweenEmptyMethodCallParentheses": False,
|
||||
"SpaceAfterControlFlowStatementKeyword": True,
|
||||
"SpaceWithinExpressionParentheses": False,
|
||||
"SpaceWithinCastParentheses": False,
|
||||
"SpaceWithinOtherParentheses": False,
|
||||
"SpaceAfterCast": False,
|
||||
"SpaceBeforeOpenSquareBracket": False,
|
||||
"SpaceBetweenEmptySquareBrackets": False,
|
||||
"SpaceWithinSquareBrackets": False,
|
||||
"SpaceAfterColonInBaseTypeDeclaration": True,
|
||||
"SpaceAfterComma": True,
|
||||
"SpaceAfterDot": False,
|
||||
"SpaceAfterSemicolonsInForStatement": True,
|
||||
"SpaceBeforeColonInBaseTypeDeclaration": True,
|
||||
"SpaceBeforeComma": False,
|
||||
"SpaceBeforeDot": False,
|
||||
"SpaceBeforeSemicolonsInForStatement": False,
|
||||
"SpacingAroundBinaryOperator": "single",
|
||||
"IndentBraces": False,
|
||||
"IndentBlock": True,
|
||||
"IndentSwitchSection": True,
|
||||
"IndentSwitchCaseSection": True,
|
||||
"IndentSwitchCaseSectionWhenBlock": True,
|
||||
"LabelPositioning": "oneLess",
|
||||
"WrappingPreserveSingleLine": True,
|
||||
"WrappingKeepStatementsOnSingleLine": True,
|
||||
"NewLinesForBracesInTypes": True,
|
||||
"NewLinesForBracesInMethods": True,
|
||||
"NewLinesForBracesInProperties": True,
|
||||
"NewLinesForBracesInAccessors": True,
|
||||
"NewLinesForBracesInAnonymousMethods": True,
|
||||
"NewLinesForBracesInControlBlocks": True,
|
||||
"NewLinesForBracesInAnonymousTypes": True,
|
||||
"NewLinesForBracesInObjectCollectionArrayInitializers": True,
|
||||
"NewLinesForBracesInLambdaExpressionBody": True,
|
||||
"NewLineForElse": True,
|
||||
"NewLineForCatch": True,
|
||||
"NewLineForFinally": True,
|
||||
"NewLineForMembersInObjectInit": True,
|
||||
"NewLineForMembersInAnonymousTypes": True,
|
||||
"NewLineForClausesInQuery": True,
|
||||
},
|
||||
"FileOptions": {
|
||||
"SystemExcludeSearchPatterns": [
|
||||
"**/node_modules/**/*",
|
||||
"**/bin/**/*",
|
||||
"**/obj/**/*",
|
||||
"**/.git/**/*",
|
||||
"**/.git",
|
||||
"**/.svn",
|
||||
"**/.hg",
|
||||
"**/CVS",
|
||||
"**/.DS_Store",
|
||||
"**/Thumbs.db",
|
||||
],
|
||||
"ExcludeSearchPatterns": [],
|
||||
},
|
||||
"RenameOptions": {
|
||||
"RenameOverloads": False,
|
||||
"RenameInStrings": False,
|
||||
"RenameInComments": False,
|
||||
},
|
||||
"ImplementTypeOptions": {
|
||||
"InsertionBehavior": 0,
|
||||
"PropertyGenerationBehavior": 0,
|
||||
},
|
||||
"DotNetCliOptions": {"LocationPaths": None},
|
||||
"Plugins": {"LocationPaths": None},
|
||||
}
|
||||
]
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("language/status", lang_status_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
self.server.on_request("workspace/configuration", workspace_configuration_handler)
|
||||
|
||||
self.logger.log("Starting OmniSharp server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
self.server.notify.initialized({})
|
||||
with open(os.path.join(os.path.dirname(__file__), "workspace_did_change_configuration.json"), "r", encoding="utf-8") as f:
|
||||
self.server.notify.workspace_did_change_configuration({
|
||||
"settings": json.load(f)
|
||||
})
|
||||
assert "capabilities" in init_response
|
||||
if (
|
||||
"definitionProvider" in init_response["capabilities"]
|
||||
and init_response["capabilities"]["definitionProvider"]
|
||||
):
|
||||
self.definition_available.set()
|
||||
if (
|
||||
"referencesProvider" in init_response["capabilities"]
|
||||
and init_response["capabilities"]["referencesProvider"]
|
||||
):
|
||||
self.references_available.set()
|
||||
|
||||
self.definition_available.wait()
|
||||
self.references_available.wait()
|
||||
@@ -0,0 +1,441 @@
|
||||
{
|
||||
"_description": "Used to download the runtime dependencies for running OmniSharp. Obtained from https://github.com/dotnet/vscode-csharp/blob/main/package.json",
|
||||
"runtimeDependencies": [
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Windows (.NET 4 / x86)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x86-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"x86"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10/OmniSharp.exe",
|
||||
"platformId": "win-x86",
|
||||
"isFramework": true,
|
||||
"integrity": "C81CE2099AD494EF63F9D88FAA70D55A68CF175810F944526FF94AAC7A5109F9",
|
||||
"dotnet_version": "4",
|
||||
"binaryName": "OmniSharp.exe"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Windows (.NET 6 / x86)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x86-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"x86"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "win-x86",
|
||||
"isFramework": false,
|
||||
"integrity": "B7E62415CFC3DAC2154AC636C5BF0FB4B2C9BBF11B5A1FBF72381DDDED59791E",
|
||||
"dotnet_version": "6",
|
||||
"binaryName": "OmniSharp.exe"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Windows (.NET 4 / x64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x64-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10/OmniSharp.exe",
|
||||
"platformId": "win-x64",
|
||||
"isFramework": true,
|
||||
"integrity": "BE0ED10AACEA17E14B78BD0D887DE5935D4ECA3712192A701F3F2100CA3C8B6E",
|
||||
"dotnet_version": "4",
|
||||
"binaryName": "OmniSharp.exe"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Windows (.NET 6 / x64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-x64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "win-x64",
|
||||
"isFramework": false,
|
||||
"integrity": "A73327395E7EF92C1D8E307055463DA412662C03F077ECC743462FD2760BB537",
|
||||
"dotnet_version": "6",
|
||||
"binaryName": "OmniSharp.exe"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Windows (.NET 4 / arm64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-arm64-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10/OmniSharp.exe",
|
||||
"platformId": "win-arm64",
|
||||
"isFramework": true,
|
||||
"integrity": "32FA0067B0639F87760CD1A769B16E6A53588C137C4D31661836CA4FB28D3DD6",
|
||||
"dotnet_version": "4",
|
||||
"binaryName": "OmniSharp.exe"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Windows (.NET 6 / arm64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-win-arm64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "win-arm64",
|
||||
"isFramework": false,
|
||||
"integrity": "433F9B360CAA7B4DDD85C604D5C5542C1A718BCF2E71B2BCFC7526E6D41F4E8F",
|
||||
"dotnet_version": "6",
|
||||
"binaryName": "OmniSharp.exe"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for OSX (Mono / x64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-osx-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64",
|
||||
"arm64"
|
||||
],
|
||||
"binaries": [
|
||||
"./mono.osx",
|
||||
"./run"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10/run",
|
||||
"platformId": "osx",
|
||||
"isFramework": true,
|
||||
"integrity": "2CC42F0EC7C30CFA8858501D12ECB6FB685A1FCFB8ECB35698A4B12406551968",
|
||||
"dotnet_version": "mono"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for OSX (.NET 6 / x64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-osx-x64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "osx-x64",
|
||||
"isFramework": false,
|
||||
"integrity": "C9D6E9F2C839A66A7283AE6A9EC545EE049B48EB230D33E91A6322CB67FF9D97",
|
||||
"dotnet_version": "6"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for OSX (.NET 6 / arm64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-osx-arm64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "osx-arm64",
|
||||
"isFramework": false,
|
||||
"integrity": "851350F52F83E3BAD5A92D113E4B9882FCD1DEB16AA84FF94B6F2CEE3C70051E",
|
||||
"dotnet_version": "6"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Linux (Mono / x86)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-x86-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"architectures": [
|
||||
"x86",
|
||||
"i686"
|
||||
],
|
||||
"binaries": [
|
||||
"./mono.linux-x86",
|
||||
"./run"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10/run",
|
||||
"platformId": "linux-x86",
|
||||
"isFramework": true,
|
||||
"integrity": "474B1CDBAE64CFEC655FB6B0659BCE481023C48274441C72991E67B6E13E56A1",
|
||||
"dotnet_version": "mono"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Linux (Mono / x64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-x64-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"binaries": [
|
||||
"./mono.linux-x86_64",
|
||||
"./run"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10/run",
|
||||
"platformId": "linux-x64",
|
||||
"isFramework": true,
|
||||
"integrity": "FB4CAA47343265100349375D79DBCCE1868950CED675CB07FCBE8462EDBCDD37",
|
||||
"dotnet_version": "mono"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Linux (.NET 6 / x64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-x64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "linux-x64",
|
||||
"isFramework": false,
|
||||
"integrity": "0926D3BEA060BF4373356B2FC0A68C10D0DE1B1150100B551BA5932814CE51E2",
|
||||
"dotnet_version": "6",
|
||||
"binaryName": "OmniSharp"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Linux (Mono / arm64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-arm64-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"binaries": [
|
||||
"./mono.linux-arm64",
|
||||
"./run"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10/run",
|
||||
"platformId": "linux-arm64",
|
||||
"isFramework": true,
|
||||
"integrity": "478F3594DFD0167E9A56E36F0364A86C73F8132A3E7EA916CA1419EFE141D2CC",
|
||||
"dotnet_version": "mono"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Linux (.NET 6 / arm64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-arm64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "linux-arm64",
|
||||
"isFramework": false,
|
||||
"integrity": "6FB6A572043A74220A92F6C19C7BB0C3743321C7563A815FD2702EF4FA7D688E",
|
||||
"dotnet_version": "6"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Linux musl (.NET 6 / x64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-musl-x64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"linux-musl"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "linux-musl-x64",
|
||||
"isFramework": false,
|
||||
"integrity": "6BFDA3AD11DBB0C6514B86ECC3E1597CC41C6E309B7575F7C599E07D9E2AE610",
|
||||
"dotnet_version": "6"
|
||||
},
|
||||
{
|
||||
"id": "OmniSharp",
|
||||
"description": "OmniSharp for Linux musl (.NET 6 / arm64)",
|
||||
"url": "https://roslynomnisharp.blob.core.windows.net/releases/1.39.10/omnisharp-linux-musl-arm64-net6.0-1.39.10.zip",
|
||||
"installPath": ".omnisharp/1.39.10-net6.0",
|
||||
"platforms": [
|
||||
"linux-musl"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"installTestPath": "./.omnisharp/1.39.10-net6.0/OmniSharp.dll",
|
||||
"platformId": "linux-musl-arm64",
|
||||
"isFramework": false,
|
||||
"integrity": "DA63619EA024EB9BBF6DB5A85C6150CAB5C0BD554544A3596ED1B17F926D6875",
|
||||
"dotnet_version": "6"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (Windows / x64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/8d42e62ea4051381c219b3e31bc4eced/razorlanguageserver-win-x64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"platformId": "win-x64",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (Windows / x86)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/e440c4f3a4a96334fe177513935fa010/razorlanguageserver-win-x86-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"x86"
|
||||
],
|
||||
"platformId": "win-x86",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (Windows / ARM64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/4ef26e45cf32fe8d51c0e7dd21f1fef6/razorlanguageserver-win-arm64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"win32"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"platformId": "win-arm64",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (Linux / x64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/6d4e23a3c7cf0465743950a39515a716/razorlanguageserver-linux-x64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-x64",
|
||||
"dll_path": "OmniSharpPlugin/Microsoft.AspNetCore.Razor.OmniSharpPlugin.dll"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (Linux ARM64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/85deebd44647ebf65724cc291d722283/razorlanguageserver-linux-arm64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"linux"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-arm64"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (Linux musl / x64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/4f0caa94ae182785655efb15eafcef23/razorlanguageserver-linux-musl-x64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"linux-musl"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-musl-x64"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (Linux musl ARM64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/0a24828206a6f3b4bc743d058ef88ce7/razorlanguageserver-linux-musl-arm64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"linux-musl"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "linux-musl-arm64"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (macOS / x64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/2afcafaf41082989efcc10405abb9314/razorlanguageserver-osx-x64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
],
|
||||
"architectures": [
|
||||
"x86_64"
|
||||
],
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "osx-x64"
|
||||
},
|
||||
{
|
||||
"id": "RazorOmnisharp",
|
||||
"description": "Razor Language Server for OmniSharp (macOS ARM64)",
|
||||
"url": "https://download.visualstudio.microsoft.com/download/pr/aee63398-023f-48db-bba2-30162c68f0c4/8bf2ed2f00d481a5987e3eb5165afddd/razorlanguageserver-osx-arm64-7.0.0-preview.23363.1.zip",
|
||||
"installPath": ".razoromnisharp",
|
||||
"platforms": [
|
||||
"darwin"
|
||||
],
|
||||
"architectures": [
|
||||
"arm64"
|
||||
],
|
||||
"binaries": [
|
||||
"./rzls"
|
||||
],
|
||||
"platformId": "osx-arm64"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
{
|
||||
"RoslynExtensionsOptions": {
|
||||
"EnableDecompilationSupport": false,
|
||||
"EnableAnalyzersSupport": true,
|
||||
"EnableImportCompletion": true,
|
||||
"EnableAsyncCompletion": false,
|
||||
"DocumentAnalysisTimeoutMs": 30000,
|
||||
"DiagnosticWorkersThreadCount": 18,
|
||||
"AnalyzeOpenDocumentsOnly": true,
|
||||
"InlayHintsOptions": {
|
||||
"EnableForParameters": false,
|
||||
"ForLiteralParameters": false,
|
||||
"ForIndexerParameters": false,
|
||||
"ForObjectCreationParameters": false,
|
||||
"ForOtherParameters": false,
|
||||
"SuppressForParametersThatDifferOnlyBySuffix": false,
|
||||
"SuppressForParametersThatMatchMethodIntent": false,
|
||||
"SuppressForParametersThatMatchArgumentName": false,
|
||||
"EnableForTypes": false,
|
||||
"ForImplicitVariableTypes": false,
|
||||
"ForLambdaParameterTypes": false,
|
||||
"ForImplicitObjectCreation": false
|
||||
},
|
||||
"LocationPaths": null
|
||||
},
|
||||
"FormattingOptions": {
|
||||
"OrganizeImports": false,
|
||||
"EnableEditorConfigSupport": true,
|
||||
"NewLine": "\n",
|
||||
"UseTabs": false,
|
||||
"TabSize": 4,
|
||||
"IndentationSize": 4,
|
||||
"SpacingAfterMethodDeclarationName": false,
|
||||
"SeparateImportDirectiveGroups": false,
|
||||
"SpaceWithinMethodDeclarationParenthesis": false,
|
||||
"SpaceBetweenEmptyMethodDeclarationParentheses": false,
|
||||
"SpaceAfterMethodCallName": false,
|
||||
"SpaceWithinMethodCallParentheses": false,
|
||||
"SpaceBetweenEmptyMethodCallParentheses": false,
|
||||
"SpaceAfterControlFlowStatementKeyword": true,
|
||||
"SpaceWithinExpressionParentheses": false,
|
||||
"SpaceWithinCastParentheses": false,
|
||||
"SpaceWithinOtherParentheses": false,
|
||||
"SpaceAfterCast": false,
|
||||
"SpaceBeforeOpenSquareBracket": false,
|
||||
"SpaceBetweenEmptySquareBrackets": false,
|
||||
"SpaceWithinSquareBrackets": false,
|
||||
"SpaceAfterColonInBaseTypeDeclaration": true,
|
||||
"SpaceAfterComma": true,
|
||||
"SpaceAfterDot": false,
|
||||
"SpaceAfterSemicolonsInForStatement": true,
|
||||
"SpaceBeforeColonInBaseTypeDeclaration": true,
|
||||
"SpaceBeforeComma": false,
|
||||
"SpaceBeforeDot": false,
|
||||
"SpaceBeforeSemicolonsInForStatement": false,
|
||||
"SpacingAroundBinaryOperator": "single",
|
||||
"IndentBraces": false,
|
||||
"IndentBlock": true,
|
||||
"IndentSwitchSection": true,
|
||||
"IndentSwitchCaseSection": true,
|
||||
"IndentSwitchCaseSectionWhenBlock": true,
|
||||
"LabelPositioning": "oneLess",
|
||||
"WrappingPreserveSingleLine": true,
|
||||
"WrappingKeepStatementsOnSingleLine": true,
|
||||
"NewLinesForBracesInTypes": true,
|
||||
"NewLinesForBracesInMethods": true,
|
||||
"NewLinesForBracesInProperties": true,
|
||||
"NewLinesForBracesInAccessors": true,
|
||||
"NewLinesForBracesInAnonymousMethods": true,
|
||||
"NewLinesForBracesInControlBlocks": true,
|
||||
"NewLinesForBracesInAnonymousTypes": true,
|
||||
"NewLinesForBracesInObjectCollectionArrayInitializers": true,
|
||||
"NewLinesForBracesInLambdaExpressionBody": true,
|
||||
"NewLineForElse": true,
|
||||
"NewLineForCatch": true,
|
||||
"NewLineForFinally": true,
|
||||
"NewLineForMembersInObjectInit": true,
|
||||
"NewLineForMembersInAnonymousTypes": true,
|
||||
"NewLineForClausesInQuery": true
|
||||
},
|
||||
"FileOptions": {
|
||||
"SystemExcludeSearchPatterns": [
|
||||
"**/node_modules/**/*",
|
||||
"**/bin/**/*",
|
||||
"**/obj/**/*",
|
||||
"**/.git/**/*",
|
||||
"**/.git",
|
||||
"**/.svn",
|
||||
"**/.hg",
|
||||
"**/CVS",
|
||||
"**/.DS_Store",
|
||||
"**/Thumbs.db"
|
||||
],
|
||||
"ExcludeSearchPatterns": []
|
||||
},
|
||||
"RenameOptions": {
|
||||
"RenameOverloads": false,
|
||||
"RenameInStrings": false,
|
||||
"RenameInComments": false
|
||||
},
|
||||
"ImplementTypeOptions": {
|
||||
"InsertionBehavior": 0,
|
||||
"PropertyGenerationBehavior": 0
|
||||
},
|
||||
"DotNetCliOptions": {
|
||||
"LocationPaths": null
|
||||
},
|
||||
"Plugins": {
|
||||
"LocationPaths": null
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,238 @@
|
||||
"""
|
||||
Provides Python specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import re
|
||||
import threading
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class PyrightServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Python specific instantiation of the LanguageServer class using Pyright.
|
||||
Contains various configurations and settings specific to Python.
|
||||
"""
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a PyrightServer instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
# Note 1: we can also use `pyright-langserver --stdio` but it requires pyright to be installed with npm
|
||||
# Note 2: we can also use `bpyright-langserver --stdio` if we ever are unhappy with pyright
|
||||
ProcessLaunchInfo(cmd="python -m pyright.langserver --stdio", cwd=repository_root_path),
|
||||
"python",
|
||||
)
|
||||
|
||||
# Event to signal when initial workspace analysis is complete
|
||||
self.analysis_complete = threading.Event()
|
||||
self.found_source_files = False
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["venv", "__pycache__"]
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Pyright Language Server.
|
||||
"""
|
||||
# Create basic initialization parameters
|
||||
initialize_params: InitializeParams = { # type: ignore
|
||||
"processId": os.getpid(),
|
||||
"rootPath": repository_absolute_path,
|
||||
"rootUri": pathlib.Path(repository_absolute_path).as_uri(),
|
||||
"initializationOptions": {
|
||||
"exclude": [
|
||||
"**/__pycache__",
|
||||
"**/.venv",
|
||||
"**/.env",
|
||||
"**/build",
|
||||
"**/dist",
|
||||
"**/.pixi",
|
||||
],
|
||||
"reportMissingImports": "error",
|
||||
},
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": True,
|
||||
"workspaceEdit": {"documentChanges": True},
|
||||
"didChangeConfiguration": {"dynamicRegistration": True},
|
||||
"didChangeWatchedFiles": {"dynamicRegistration": True},
|
||||
"symbol": {
|
||||
"dynamicRegistration": True,
|
||||
"symbolKind": {
|
||||
"valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
|
||||
},
|
||||
},
|
||||
"executeCommand": {"dynamicRegistration": True},
|
||||
},
|
||||
"textDocument": {
|
||||
"synchronization": {"dynamicRegistration": True, "willSave": True, "willSaveWaitUntil": True, "didSave": True},
|
||||
"completion": {
|
||||
"dynamicRegistration": True,
|
||||
"contextSupport": True,
|
||||
"completionItem": {
|
||||
"snippetSupport": True,
|
||||
"commitCharactersSupport": True,
|
||||
"documentationFormat": ["markdown", "plaintext"],
|
||||
"deprecatedSupport": True,
|
||||
"preselectSupport": True,
|
||||
},
|
||||
"completionItemKind": {
|
||||
"valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25]
|
||||
},
|
||||
},
|
||||
"hover": {"dynamicRegistration": True, "contentFormat": ["markdown", "plaintext"]},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": True,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": ["markdown", "plaintext"],
|
||||
"parameterInformation": {"labelOffsetSupport": True},
|
||||
},
|
||||
},
|
||||
"definition": {"dynamicRegistration": True},
|
||||
"references": {"dynamicRegistration": True},
|
||||
"documentHighlight": {"dynamicRegistration": True},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": True,
|
||||
"symbolKind": {
|
||||
"valueSet": [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": True,
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": True,
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports",
|
||||
]
|
||||
}
|
||||
},
|
||||
},
|
||||
"codeLens": {"dynamicRegistration": True},
|
||||
"formatting": {"dynamicRegistration": True},
|
||||
"rangeFormatting": {"dynamicRegistration": True},
|
||||
"onTypeFormatting": {"dynamicRegistration": True},
|
||||
"rename": {"dynamicRegistration": True},
|
||||
"publishDiagnostics": {"relatedInformation": True},
|
||||
},
|
||||
},
|
||||
"workspaceFolders": [
|
||||
{"uri": pathlib.Path(repository_absolute_path).as_uri(), "name": os.path.basename(repository_absolute_path)}
|
||||
],
|
||||
}
|
||||
|
||||
return initialize_params
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the Pyright Language Server and waits for initial workspace analysis to complete.
|
||||
|
||||
This prevents zombie processes by ensuring Pyright has finished its initial background
|
||||
tasks before we consider the server ready.
|
||||
|
||||
Usage:
|
||||
```
|
||||
async with lsp.start_server():
|
||||
# LanguageServer has been initialized and workspace analysis is complete
|
||||
await lsp.request_definition(...)
|
||||
await lsp.request_references(...)
|
||||
# Shutdown the LanguageServer on exit from scope
|
||||
# LanguageServer has been shutdown cleanly
|
||||
```
|
||||
"""
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def window_log_message(msg):
|
||||
"""
|
||||
Monitor Pyright's log messages to detect when initial analysis is complete.
|
||||
Pyright logs "Found X source files" when it finishes scanning the workspace.
|
||||
"""
|
||||
message_text = msg.get("message", "")
|
||||
self.logger.log(f"LSP: window/logMessage: {message_text}", logging.INFO)
|
||||
|
||||
# Look for "Found X source files" which indicates workspace scanning is complete
|
||||
# Unfortunately, pyright is unreliable and there seems to be no better way
|
||||
if re.search(r"Found \d+ source files?", message_text):
|
||||
self.logger.log("Pyright workspace scanning complete", logging.INFO)
|
||||
self.found_source_files = True
|
||||
self.analysis_complete.set()
|
||||
self.completions_available.set()
|
||||
|
||||
def check_experimental_status(params):
|
||||
"""
|
||||
Also listen for experimental/serverStatus as a backup signal
|
||||
"""
|
||||
if params.get("quiescent") == True:
|
||||
self.logger.log("Received experimental/serverStatus with quiescent=true", logging.INFO)
|
||||
if not self.found_source_files:
|
||||
self.analysis_complete.set()
|
||||
self.completions_available.set()
|
||||
|
||||
# Set up notification handlers
|
||||
self.server.on_request("client/registerCapability", do_nothing)
|
||||
self.server.on_notification("language/status", do_nothing)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
|
||||
self.logger.log("Starting pyright-langserver server process", logging.INFO)
|
||||
self.server.start()
|
||||
|
||||
# Send proper initialization parameters
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to pyright server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
self.logger.log(f"Received initialize response from pyright server: {init_response}", logging.INFO)
|
||||
|
||||
# Verify that the server supports our required features
|
||||
assert "textDocumentSync" in init_response["capabilities"]
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert "definitionProvider" in init_response["capabilities"]
|
||||
|
||||
# Complete the initialization handshake
|
||||
self.server.notify.initialized({})
|
||||
|
||||
# Wait for Pyright to complete its initial workspace analysis
|
||||
# This prevents zombie processes by ensuring background tasks finish
|
||||
self.logger.log("Waiting for Pyright to complete initial workspace analysis...", logging.INFO)
|
||||
if self.analysis_complete.wait(timeout=5.0):
|
||||
self.logger.log("Pyright initial analysis complete, server ready", logging.INFO)
|
||||
else:
|
||||
self.logger.log("Timeout waiting for Pyright analysis completion, proceeding anyway", logging.WARNING)
|
||||
# Fallback: assume analysis is complete after timeout
|
||||
self.analysis_complete.set()
|
||||
self.completions_available.set()
|
||||
@@ -0,0 +1,917 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"clientInfo": {
|
||||
"name": "Visual Studio Code - Insiders",
|
||||
"version": "1.82.0-insider"
|
||||
},
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": true,
|
||||
"workspaceEdit": {
|
||||
"documentChanges": true,
|
||||
"resourceOperations": [
|
||||
"create",
|
||||
"rename",
|
||||
"delete"
|
||||
],
|
||||
"failureHandling": "textOnlyTransactional",
|
||||
"normalizesLineEndings": true,
|
||||
"changeAnnotationSupport": {
|
||||
"groupsOnLabel": true
|
||||
}
|
||||
},
|
||||
"configuration": true,
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": true,
|
||||
"relativePatternSupport": true
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"location.range"
|
||||
]
|
||||
}
|
||||
},
|
||||
"codeLens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"workspaceFolders": true,
|
||||
"semanticTokens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"fileOperations": {
|
||||
"dynamicRegistration": true,
|
||||
"didCreate": true,
|
||||
"didRename": true,
|
||||
"didDelete": true,
|
||||
"willCreate": true,
|
||||
"willRename": true,
|
||||
"willDelete": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"refreshSupport": true
|
||||
}
|
||||
},
|
||||
"textDocument": {
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": true,
|
||||
"versionSupport": false,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"codeDescriptionSupport": true,
|
||||
"dataSupport": true
|
||||
},
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"willSave": true,
|
||||
"willSaveWaitUntil": true,
|
||||
"didSave": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"contextSupport": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true,
|
||||
"commitCharactersSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"deprecatedSupport": true,
|
||||
"preselectSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"insertReplaceSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"documentation",
|
||||
"detail",
|
||||
"additionalTextEdits"
|
||||
]
|
||||
},
|
||||
"insertTextModeSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"labelDetailsSupport": true
|
||||
},
|
||||
"insertTextMode": 2,
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"completionList": {
|
||||
"itemDefaults": [
|
||||
"commitCharacters",
|
||||
"editRange",
|
||||
"insertTextFormat",
|
||||
"insertTextMode"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": true,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"parameterInformation": {
|
||||
"labelOffsetSupport": true
|
||||
},
|
||||
"activeParameterSupport": true
|
||||
},
|
||||
"contextSupport": true
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"labelSupport": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"isPreferredSupport": true,
|
||||
"disabledSupport": true,
|
||||
"dataSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
},
|
||||
"honorsChangeAnnotations": false
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": true,
|
||||
"prepareSupport": true,
|
||||
"prepareSupportDefaultBehavior": 1,
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": true,
|
||||
"tooltipSupport": true
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"foldingRange": {
|
||||
"dynamicRegistration": true,
|
||||
"rangeLimit": 5000,
|
||||
"lineFoldingOnly": true,
|
||||
"foldingRangeKind": {
|
||||
"valueSet": [
|
||||
"comment",
|
||||
"imports",
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"foldingRange": {
|
||||
"collapsedText": false
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"selectionRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"callHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"dynamicRegistration": true,
|
||||
"tokenTypes": [
|
||||
"namespace",
|
||||
"type",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"struct",
|
||||
"typeParameter",
|
||||
"parameter",
|
||||
"variable",
|
||||
"property",
|
||||
"enumMember",
|
||||
"event",
|
||||
"function",
|
||||
"method",
|
||||
"macro",
|
||||
"keyword",
|
||||
"modifier",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"decorator"
|
||||
],
|
||||
"tokenModifiers": [
|
||||
"declaration",
|
||||
"definition",
|
||||
"readonly",
|
||||
"static",
|
||||
"deprecated",
|
||||
"abstract",
|
||||
"async",
|
||||
"modification",
|
||||
"documentation",
|
||||
"defaultLibrary"
|
||||
],
|
||||
"formats": [
|
||||
"relative"
|
||||
],
|
||||
"requests": {
|
||||
"range": true,
|
||||
"full": {
|
||||
"delta": true
|
||||
}
|
||||
},
|
||||
"multilineTokenSupport": false,
|
||||
"overlappingTokenSupport": false,
|
||||
"serverCancelSupport": true,
|
||||
"augmentsSyntaxTokens": false
|
||||
},
|
||||
"linkedEditingRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"dynamicRegistration": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"tooltip",
|
||||
"textEdits",
|
||||
"label.tooltip",
|
||||
"label.location",
|
||||
"label.command"
|
||||
]
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"dynamicRegistration": true,
|
||||
"relatedDocumentSupport": false
|
||||
}
|
||||
},
|
||||
"window": {
|
||||
"showMessage": {
|
||||
"messageActionItem": {
|
||||
"additionalPropertiesSupport": true
|
||||
}
|
||||
},
|
||||
"showDocument": {
|
||||
"support": true
|
||||
},
|
||||
"workDoneProgress": true
|
||||
},
|
||||
"general": {
|
||||
"staleRequestSupport": {
|
||||
"cancel": true,
|
||||
"retryOnContentModified": [
|
||||
"textDocument/semanticTokens/full",
|
||||
"textDocument/semanticTokens/range",
|
||||
"textDocument/semanticTokens/full/delta"
|
||||
]
|
||||
},
|
||||
"regularExpressions": {
|
||||
"engine": "ECMAScript",
|
||||
"version": "ES2020"
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "marked",
|
||||
"version": "1.1.0",
|
||||
"allowedTags": [
|
||||
"ul",
|
||||
"li",
|
||||
"p",
|
||||
"code",
|
||||
"blockquote",
|
||||
"ol",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"hr",
|
||||
"em",
|
||||
"pre",
|
||||
"table",
|
||||
"thead",
|
||||
"tbody",
|
||||
"tr",
|
||||
"th",
|
||||
"td",
|
||||
"div",
|
||||
"del",
|
||||
"a",
|
||||
"strong",
|
||||
"br",
|
||||
"img",
|
||||
"span"
|
||||
]
|
||||
},
|
||||
"positionEncodings": [
|
||||
"utf-16"
|
||||
]
|
||||
},
|
||||
"notebookDocument": {
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"executionSummarySupport": true
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"snippetTextEdit": true,
|
||||
"codeActionGroup": true,
|
||||
"hoverActions": true,
|
||||
"serverStatusNotification": true,
|
||||
"colorDiagnosticOutput": true,
|
||||
"openServerLogs": true,
|
||||
"localDocs": true,
|
||||
"commands": {
|
||||
"commands": [
|
||||
"rust-analyzer.runSingle",
|
||||
"rust-analyzer.debugSingle",
|
||||
"rust-analyzer.showReferences",
|
||||
"rust-analyzer.gotoLocation",
|
||||
"editor.action.triggerParameterHints"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"initializationOptions": {
|
||||
"cargoRunner": null,
|
||||
"runnables": {
|
||||
"extraEnv": null,
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"command": null,
|
||||
"extraArgs": []
|
||||
},
|
||||
"statusBar": {
|
||||
"clickAction": "openLogs"
|
||||
},
|
||||
"server": {
|
||||
"path": null,
|
||||
"extraEnv": null
|
||||
},
|
||||
"trace": {
|
||||
"server": "verbose",
|
||||
"extension": false
|
||||
},
|
||||
"debug": {
|
||||
"engine": "auto",
|
||||
"sourceFileMap": {
|
||||
"/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
|
||||
},
|
||||
"openDebugPane": false,
|
||||
"engineSettings": {}
|
||||
},
|
||||
"restartServerOnConfigChange": false,
|
||||
"typing": {
|
||||
"continueCommentsOnNewline": true,
|
||||
"autoClosingAngleBrackets": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"previewRustcOutput": false,
|
||||
"useRustcErrorCode": false,
|
||||
"disabled": [],
|
||||
"enable": true,
|
||||
"experimental": {
|
||||
"enable": false
|
||||
},
|
||||
"remapPrefix": {},
|
||||
"warningsAsHint": [],
|
||||
"warningsAsInfo": []
|
||||
},
|
||||
"discoverProjectRunner": null,
|
||||
"showUnlinkedFileNotification": true,
|
||||
"showDependenciesExplorer": true,
|
||||
"assist": {
|
||||
"emitMustUse": false,
|
||||
"expressionFillDefault": "todo"
|
||||
},
|
||||
"cachePriming": {
|
||||
"enable": true,
|
||||
"numThreads": 0
|
||||
},
|
||||
"cargo": {
|
||||
"autoreload": true,
|
||||
"buildScripts": {
|
||||
"enable": true,
|
||||
"invocationLocation": "workspace",
|
||||
"invocationStrategy": "per_workspace",
|
||||
"overrideCommand": null,
|
||||
"useRustcWrapper": true
|
||||
},
|
||||
"cfgs": {},
|
||||
"extraArgs": [],
|
||||
"extraEnv": {},
|
||||
"features": [],
|
||||
"noDefaultFeatures": false,
|
||||
"sysroot": "discover",
|
||||
"sysrootSrc": null,
|
||||
"target": null,
|
||||
"unsetTest": [
|
||||
"core"
|
||||
]
|
||||
},
|
||||
"checkOnSave": true,
|
||||
"check": {
|
||||
"allTargets": true,
|
||||
"command": "check",
|
||||
"extraArgs": [],
|
||||
"extraEnv": {},
|
||||
"features": null,
|
||||
"ignore": [],
|
||||
"invocationLocation": "workspace",
|
||||
"invocationStrategy": "per_workspace",
|
||||
"noDefaultFeatures": null,
|
||||
"overrideCommand": null,
|
||||
"targets": null
|
||||
},
|
||||
"completion": {
|
||||
"autoimport": {
|
||||
"enable": true
|
||||
},
|
||||
"autoself": {
|
||||
"enable": true
|
||||
},
|
||||
"callable": {
|
||||
"snippets": "fill_arguments"
|
||||
},
|
||||
"fullFunctionSignatures": {
|
||||
"enable": false
|
||||
},
|
||||
"limit": null,
|
||||
"postfix": {
|
||||
"enable": true
|
||||
},
|
||||
"privateEditable": {
|
||||
"enable": false
|
||||
},
|
||||
"snippets": {
|
||||
"custom": {
|
||||
"Arc::new": {
|
||||
"postfix": "arc",
|
||||
"body": "Arc::new(${receiver})",
|
||||
"requires": "std::sync::Arc",
|
||||
"description": "Put the expression into an `Arc`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Rc::new": {
|
||||
"postfix": "rc",
|
||||
"body": "Rc::new(${receiver})",
|
||||
"requires": "std::rc::Rc",
|
||||
"description": "Put the expression into an `Rc`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Box::pin": {
|
||||
"postfix": "pinbox",
|
||||
"body": "Box::pin(${receiver})",
|
||||
"requires": "std::boxed::Box",
|
||||
"description": "Put the expression into a pinned `Box`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Ok": {
|
||||
"postfix": "ok",
|
||||
"body": "Ok(${receiver})",
|
||||
"description": "Wrap the expression in a `Result::Ok`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Err": {
|
||||
"postfix": "err",
|
||||
"body": "Err(${receiver})",
|
||||
"description": "Wrap the expression in a `Result::Err`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Some": {
|
||||
"postfix": "some",
|
||||
"body": "Some(${receiver})",
|
||||
"description": "Wrap the expression in an `Option::Some`",
|
||||
"scope": "expr"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"excludeDirs": [],
|
||||
"watcher": "client"
|
||||
},
|
||||
"highlightRelated": {
|
||||
"breakPoints": {
|
||||
"enable": true
|
||||
},
|
||||
"closureCaptures": {
|
||||
"enable": true
|
||||
},
|
||||
"exitPoints": {
|
||||
"enable": true
|
||||
},
|
||||
"references": {
|
||||
"enable": true
|
||||
},
|
||||
"yieldPoints": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"actions": {
|
||||
"debug": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"gotoTypeDef": {
|
||||
"enable": true
|
||||
},
|
||||
"implementations": {
|
||||
"enable": true
|
||||
},
|
||||
"references": {
|
||||
"enable": false
|
||||
},
|
||||
"run": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"documentation": {
|
||||
"enable": true,
|
||||
"keywords": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"enable": true
|
||||
},
|
||||
"memoryLayout": {
|
||||
"alignment": "hexadecimal",
|
||||
"enable": true,
|
||||
"niches": false,
|
||||
"offset": "hexadecimal",
|
||||
"size": "both"
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"granularity": {
|
||||
"enforce": false,
|
||||
"group": "crate"
|
||||
},
|
||||
"group": {
|
||||
"enable": true
|
||||
},
|
||||
"merge": {
|
||||
"glob": true
|
||||
},
|
||||
"preferNoStd": false,
|
||||
"preferPrelude": false,
|
||||
"prefix": "plain"
|
||||
},
|
||||
"inlayHints": {
|
||||
"bindingModeHints": {
|
||||
"enable": false
|
||||
},
|
||||
"chainingHints": {
|
||||
"enable": true
|
||||
},
|
||||
"closingBraceHints": {
|
||||
"enable": true,
|
||||
"minLines": 25
|
||||
},
|
||||
"closureCaptureHints": {
|
||||
"enable": false
|
||||
},
|
||||
"closureReturnTypeHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"closureStyle": "impl_fn",
|
||||
"discriminantHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"expressionAdjustmentHints": {
|
||||
"enable": "never",
|
||||
"hideOutsideUnsafe": false,
|
||||
"mode": "prefix"
|
||||
},
|
||||
"lifetimeElisionHints": {
|
||||
"enable": "never",
|
||||
"useParameterNames": false
|
||||
},
|
||||
"maxLength": 25,
|
||||
"parameterHints": {
|
||||
"enable": true
|
||||
},
|
||||
"reborrowHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"renderColons": true,
|
||||
"typeHints": {
|
||||
"enable": true,
|
||||
"hideClosureInitialization": false,
|
||||
"hideNamedConstructor": false
|
||||
}
|
||||
},
|
||||
"interpret": {
|
||||
"tests": false
|
||||
},
|
||||
"joinLines": {
|
||||
"joinAssignments": true,
|
||||
"joinElseIf": true,
|
||||
"removeTrailingComma": true,
|
||||
"unwrapTrivialBlock": true
|
||||
},
|
||||
"lens": {
|
||||
"debug": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"forceCustomCommands": true,
|
||||
"implementations": {
|
||||
"enable": true
|
||||
},
|
||||
"location": "above_name",
|
||||
"references": {
|
||||
"adt": {
|
||||
"enable": false
|
||||
},
|
||||
"enumVariant": {
|
||||
"enable": false
|
||||
},
|
||||
"method": {
|
||||
"enable": false
|
||||
},
|
||||
"trait": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"run": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"linkedProjects": [],
|
||||
"lru": {
|
||||
"capacity": null,
|
||||
"query": {
|
||||
"capacities": {}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"cargoTomlNotFound": true
|
||||
},
|
||||
"numThreads": null,
|
||||
"procMacro": {
|
||||
"attributes": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"ignored": {},
|
||||
"server": null
|
||||
},
|
||||
"references": {
|
||||
"excludeImports": false
|
||||
},
|
||||
"rust": {
|
||||
"analyzerTargetDir": null
|
||||
},
|
||||
"rustc": {
|
||||
"source": null
|
||||
},
|
||||
"rustfmt": {
|
||||
"extraArgs": [],
|
||||
"overrideCommand": null,
|
||||
"rangeFormatting": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"semanticHighlighting": {
|
||||
"doc": {
|
||||
"comment": {
|
||||
"inject": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"nonStandardTokens": true,
|
||||
"operator": {
|
||||
"enable": true,
|
||||
"specialization": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"punctuation": {
|
||||
"enable": false,
|
||||
"separate": {
|
||||
"macro": {
|
||||
"bang": false
|
||||
}
|
||||
},
|
||||
"specialization": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"strings": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"signatureInfo": {
|
||||
"detail": "full",
|
||||
"documentation": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"symbol": {
|
||||
"search": {
|
||||
"kind": "only_types",
|
||||
"limit": 128,
|
||||
"scope": "workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"_description": "Used to download the runtime dependencies for running RustAnalyzer. Obtained from https://github.com/rust-lang/rust-analyzer/releases",
|
||||
"runtimeDependencies": [
|
||||
{
|
||||
"id": "RustAnalyzer",
|
||||
"description": "RustAnalyzer for Linux (x64)",
|
||||
"url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-aarch64-apple-darwin.gz",
|
||||
"platformId": "osx-arm64",
|
||||
"archiveType": "gz",
|
||||
"binaryName": "rust_analyzer"
|
||||
},
|
||||
{
|
||||
"id": "RustAnalyzer",
|
||||
"description": "RustAnalyzer for Linux (x64)",
|
||||
"url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-unknown-linux-gnu.gz",
|
||||
"platformId": "linux-x64",
|
||||
"archiveType": "gz",
|
||||
"binaryName": "rust_analyzer"
|
||||
},
|
||||
{
|
||||
"id": "RustAnalyzer",
|
||||
"description": "RustAnalyzer for Windows (x64)",
|
||||
"url": "https://github.com/rust-lang/rust-analyzer/releases/download/2023-10-09/rust-analyzer-x86_64-pc-windows-msvc.zip",
|
||||
"platformId": "win-x64",
|
||||
"archiveType": "zip",
|
||||
"binaryName": "rust-analyzer.exe"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
"""
|
||||
Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import threading
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_utils import FileUtils
|
||||
from multilspy.multilspy_utils import PlatformUtils
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class RustAnalyzer(SolidLanguageServer):
|
||||
"""
|
||||
Provides Rust specific instantiation of the LanguageServer class. Contains various configurations and settings specific to Rust.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a RustAnalyzer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
rustanalyzer_executable_path = self.setup_runtime_dependencies(logger, config)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=rustanalyzer_executable_path, cwd=repository_root_path),
|
||||
"rust",
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.service_ready_event = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["target"]
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for rust_analyzer.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
# assert platform_id.value in [
|
||||
# "linux-x64",
|
||||
# "win-x64",
|
||||
# ], "Only linux-x64 and win-x64 platform is supported for in multilspy at the moment"
|
||||
|
||||
runtime_dependencies = d["runtimeDependencies"]
|
||||
runtime_dependencies = [
|
||||
dependency for dependency in runtime_dependencies if dependency["platformId"] == platform_id.value
|
||||
]
|
||||
assert len(runtime_dependencies) == 1
|
||||
dependency = runtime_dependencies[0]
|
||||
|
||||
rustanalyzer_ls_dir = os.path.join(os.path.dirname(__file__), "static", "RustAnalyzer")
|
||||
rustanalyzer_executable_path = os.path.join(rustanalyzer_ls_dir, dependency["binaryName"])
|
||||
if not os.path.exists(rustanalyzer_ls_dir):
|
||||
os.makedirs(rustanalyzer_ls_dir)
|
||||
if dependency["archiveType"] == "gz":
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, dependency["url"], rustanalyzer_executable_path, dependency["archiveType"]
|
||||
)
|
||||
else:
|
||||
FileUtils.download_and_extract_archive(
|
||||
logger, dependency["url"], rustanalyzer_ls_dir, dependency["archiveType"]
|
||||
)
|
||||
assert os.path.exists(rustanalyzer_executable_path)
|
||||
os.chmod(rustanalyzer_executable_path, stat.S_IEXEC)
|
||||
|
||||
return rustanalyzer_executable_path
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Rust Analyzer Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the Rust Analyzer Language Server
|
||||
"""
|
||||
|
||||
def register_capability_handler(params):
|
||||
assert "registrations" in params
|
||||
for registration in params["registrations"]:
|
||||
if registration["method"] == "workspace/executeCommand":
|
||||
self.initialize_searcher_command_available.set()
|
||||
self.resolve_main_method_available.set()
|
||||
return
|
||||
|
||||
def lang_status_handler(params):
|
||||
# TODO: Should we wait for
|
||||
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
|
||||
# Before proceeding?
|
||||
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
|
||||
self.service_ready_event.set()
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def check_experimental_status(params):
|
||||
if params["quiescent"] == True:
|
||||
self.server_ready.set()
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("language/status", lang_status_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
|
||||
self.logger.log("Starting RustAnalyzer server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
assert init_response["capabilities"]["textDocumentSync"]["change"] == 2
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert init_response["capabilities"]["completionProvider"] == {
|
||||
"resolveProvider": True,
|
||||
"triggerCharacters": [":", ".", "'", "("],
|
||||
"completionItem": {"labelDetailsSupport": True},
|
||||
}
|
||||
self.server.notify.initialized({})
|
||||
self.completions_available.set()
|
||||
|
||||
self.server_ready.wait()
|
||||
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"_description": "This file contains the initialization parameters for the Solargraph Language Server.",
|
||||
"processId": "$processId",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"_description": "This file contains URLs and other metadata required for downloading and installing the Solargraph language server.",
|
||||
"runtimeDependencies": [
|
||||
{
|
||||
"url": "https://rubygems.org/downloads/solargraph-0.51.1.gem",
|
||||
"installCommand": "gem install solargraph -v 0.51.1",
|
||||
"binaryName": "solargraph",
|
||||
"archiveType": "gem"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
"""
|
||||
Provides Ruby specific instantiation of the LanguageServer class using Solargraph.
|
||||
Contains various configurations and settings specific to Ruby.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import stat
|
||||
import subprocess
|
||||
import threading
|
||||
from typing import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
|
||||
class Solargraph(SolidLanguageServer):
|
||||
"""
|
||||
Provides Ruby specific instantiation of the LanguageServer class using Solargraph.
|
||||
Contains various configurations and settings specific to Ruby.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a Solargraph instance. This class is not meant to be instantiated directly.
|
||||
Use LanguageServer.create() instead.
|
||||
"""
|
||||
solargraph_executable_path = self.setup_runtime_dependencies(logger, config, repository_root_path)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=f"{solargraph_executable_path} stdio", cwd=repository_root_path),
|
||||
"ruby",
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.service_ready_event = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
self.resolve_main_method_available = threading.Event()
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in ["vendor"]
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig, repository_root_path: str) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for Solargraph.
|
||||
"""
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
dependency = d["runtimeDependencies"][0]
|
||||
|
||||
# Check if Ruby is installed
|
||||
try:
|
||||
result = subprocess.run(["ruby", "--version"], check=True, capture_output=True, cwd=repository_root_path)
|
||||
ruby_version = result.stdout.strip()
|
||||
logger.log(f"Ruby version: {ruby_version}", logging.INFO)
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(f"Error checking for Ruby installation: {e.stderr}")
|
||||
except FileNotFoundError:
|
||||
raise RuntimeError("Ruby is not installed. Please install Ruby before continuing.")
|
||||
|
||||
# Check if solargraph is installed
|
||||
try:
|
||||
result = subprocess.run(["gem", "list", "^solargraph$", "-i"], check=False, capture_output=True, text=True, cwd=repository_root_path)
|
||||
if result.stdout.strip() == "false":
|
||||
logger.log("Installing Solargraph...", logging.INFO)
|
||||
subprocess.run(dependency["installCommand"].split(), check=True, capture_output=True, cwd=repository_root_path)
|
||||
|
||||
# Get the gem executable path directly
|
||||
result = subprocess.run(["gem", "which", "solargraph"], check=True, capture_output=True, text=True, cwd=repository_root_path)
|
||||
gem_path = result.stdout.strip()
|
||||
bin_dir = os.path.join(os.path.dirname(os.path.dirname(gem_path)), "bin")
|
||||
executable_path = os.path.join(bin_dir, "solargraph")
|
||||
|
||||
if not os.path.exists(executable_path):
|
||||
raise RuntimeError(f"Solargraph executable not found at {executable_path}")
|
||||
|
||||
# Ensure the executable has the right permissions
|
||||
os.chmod(executable_path, os.stat(executable_path).st_mode | stat.S_IEXEC)
|
||||
|
||||
return executable_path
|
||||
except subprocess.CalledProcessError as e:
|
||||
raise RuntimeError(f"Failed to check or install Solargraph. {e.stderr}")
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the Solargraph Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json"), "r", encoding="utf-8") as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the Solargraph Language Server for Ruby
|
||||
"""
|
||||
|
||||
def register_capability_handler(params):
|
||||
assert "registrations" in params
|
||||
for registration in params["registrations"]:
|
||||
if registration["method"] == "workspace/executeCommand":
|
||||
self.initialize_searcher_command_available.set()
|
||||
self.resolve_main_method_available.set()
|
||||
return
|
||||
|
||||
def lang_status_handler(params):
|
||||
# TODO: Should we wait for
|
||||
# server -> client: {'jsonrpc': '2.0', 'method': 'language/status', 'params': {'type': 'ProjectStatus', 'message': 'OK'}}
|
||||
# Before proceeding?
|
||||
if params["type"] == "ServiceReady" and params["message"] == "ServiceReady":
|
||||
self.service_ready_event.set()
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("language/status", lang_status_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("language/actionableNotification", do_nothing)
|
||||
|
||||
self.logger.log("Starting solargraph server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
self.logger.log(f"Sending init params: {json.dumps(initialize_params, indent=4)}", logging.INFO)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
self.logger.log(f"Received init response: {init_response}", logging.INFO)
|
||||
assert init_response["capabilities"]["textDocumentSync"] == 2
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert init_response["capabilities"]["completionProvider"] == {
|
||||
"resolveProvider": True,
|
||||
"triggerCharacters": [".", ":", "@"],
|
||||
}
|
||||
self.server.notify.initialized({})
|
||||
self.completions_available.set()
|
||||
|
||||
self.server_ready.set()
|
||||
self.server_ready.wait()
|
||||
@@ -0,0 +1,906 @@
|
||||
{
|
||||
"_description": "The parameters sent by the client when initializing the language server with the \"initialize\" request. More details at https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#initialize",
|
||||
"processId": "os.getpid()",
|
||||
"clientInfo": {
|
||||
"name": "Visual Studio Code - Insiders",
|
||||
"version": "1.82.0-insider"
|
||||
},
|
||||
"locale": "en",
|
||||
"rootPath": "$rootPath",
|
||||
"rootUri": "$rootUri",
|
||||
"capabilities": {
|
||||
"workspace": {
|
||||
"applyEdit": true,
|
||||
"workspaceEdit": {
|
||||
"documentChanges": true,
|
||||
"resourceOperations": [
|
||||
"create",
|
||||
"rename",
|
||||
"delete"
|
||||
],
|
||||
"failureHandling": "textOnlyTransactional",
|
||||
"normalizesLineEndings": true,
|
||||
"changeAnnotationSupport": {
|
||||
"groupsOnLabel": true
|
||||
}
|
||||
},
|
||||
"configuration": true,
|
||||
"didChangeWatchedFiles": {
|
||||
"dynamicRegistration": true,
|
||||
"relativePatternSupport": true
|
||||
},
|
||||
"symbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"location.range"
|
||||
]
|
||||
}
|
||||
},
|
||||
"codeLens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"executeCommand": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"didChangeConfiguration": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"workspaceFolders": true,
|
||||
"semanticTokens": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"fileOperations": {
|
||||
"dynamicRegistration": true,
|
||||
"didCreate": true,
|
||||
"didRename": true,
|
||||
"didDelete": true,
|
||||
"willCreate": true,
|
||||
"willRename": true,
|
||||
"willDelete": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"refreshSupport": true
|
||||
},
|
||||
"diagnostics": {
|
||||
"refreshSupport": true
|
||||
}
|
||||
},
|
||||
"textDocument": {
|
||||
"publishDiagnostics": {
|
||||
"relatedInformation": true,
|
||||
"versionSupport": false,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"codeDescriptionSupport": true,
|
||||
"dataSupport": true
|
||||
},
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"willSave": true,
|
||||
"willSaveWaitUntil": true,
|
||||
"didSave": true
|
||||
},
|
||||
"completion": {
|
||||
"dynamicRegistration": true,
|
||||
"contextSupport": true,
|
||||
"completionItem": {
|
||||
"snippetSupport": true,
|
||||
"commitCharactersSupport": true,
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"deprecatedSupport": true,
|
||||
"preselectSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"insertReplaceSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"documentation",
|
||||
"detail",
|
||||
"additionalTextEdits"
|
||||
]
|
||||
},
|
||||
"insertTextModeSupport": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2
|
||||
]
|
||||
},
|
||||
"labelDetailsSupport": true
|
||||
},
|
||||
"insertTextMode": 2,
|
||||
"completionItemKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25
|
||||
]
|
||||
},
|
||||
"completionList": {
|
||||
"itemDefaults": [
|
||||
"commitCharacters",
|
||||
"editRange",
|
||||
"insertTextFormat",
|
||||
"insertTextMode"
|
||||
]
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"dynamicRegistration": true,
|
||||
"contentFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
]
|
||||
},
|
||||
"signatureHelp": {
|
||||
"dynamicRegistration": true,
|
||||
"signatureInformation": {
|
||||
"documentationFormat": [
|
||||
"markdown",
|
||||
"plaintext"
|
||||
],
|
||||
"parameterInformation": {
|
||||
"labelOffsetSupport": true
|
||||
},
|
||||
"activeParameterSupport": true
|
||||
},
|
||||
"contextSupport": true
|
||||
},
|
||||
"definition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"references": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentHighlight": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"documentSymbol": {
|
||||
"dynamicRegistration": true,
|
||||
"symbolKind": {
|
||||
"valueSet": [
|
||||
1,
|
||||
2,
|
||||
3,
|
||||
4,
|
||||
5,
|
||||
6,
|
||||
7,
|
||||
8,
|
||||
9,
|
||||
10,
|
||||
11,
|
||||
12,
|
||||
13,
|
||||
14,
|
||||
15,
|
||||
16,
|
||||
17,
|
||||
18,
|
||||
19,
|
||||
20,
|
||||
21,
|
||||
22,
|
||||
23,
|
||||
24,
|
||||
25,
|
||||
26
|
||||
]
|
||||
},
|
||||
"hierarchicalDocumentSymbolSupport": true,
|
||||
"tagSupport": {
|
||||
"valueSet": [
|
||||
1
|
||||
]
|
||||
},
|
||||
"labelSupport": true
|
||||
},
|
||||
"codeAction": {
|
||||
"dynamicRegistration": true,
|
||||
"isPreferredSupport": true,
|
||||
"disabledSupport": true,
|
||||
"dataSupport": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"edit"
|
||||
]
|
||||
},
|
||||
"codeActionLiteralSupport": {
|
||||
"codeActionKind": {
|
||||
"valueSet": [
|
||||
"",
|
||||
"quickfix",
|
||||
"refactor",
|
||||
"refactor.extract",
|
||||
"refactor.inline",
|
||||
"refactor.rewrite",
|
||||
"source",
|
||||
"source.organizeImports"
|
||||
]
|
||||
}
|
||||
},
|
||||
"honorsChangeAnnotations": false
|
||||
},
|
||||
"codeLens": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"formatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rangeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"onTypeFormatting": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"rename": {
|
||||
"dynamicRegistration": true,
|
||||
"prepareSupport": true,
|
||||
"prepareSupportDefaultBehavior": 1,
|
||||
"honorsChangeAnnotations": true
|
||||
},
|
||||
"documentLink": {
|
||||
"dynamicRegistration": true,
|
||||
"tooltipSupport": true
|
||||
},
|
||||
"typeDefinition": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"implementation": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"colorProvider": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"foldingRange": {
|
||||
"dynamicRegistration": true,
|
||||
"rangeLimit": 5000,
|
||||
"lineFoldingOnly": true,
|
||||
"foldingRangeKind": {
|
||||
"valueSet": [
|
||||
"comment",
|
||||
"imports",
|
||||
"region"
|
||||
]
|
||||
},
|
||||
"foldingRange": {
|
||||
"collapsedText": false
|
||||
}
|
||||
},
|
||||
"declaration": {
|
||||
"dynamicRegistration": true,
|
||||
"linkSupport": true
|
||||
},
|
||||
"selectionRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"callHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"semanticTokens": {
|
||||
"dynamicRegistration": true,
|
||||
"tokenTypes": [
|
||||
"namespace",
|
||||
"type",
|
||||
"class",
|
||||
"enum",
|
||||
"interface",
|
||||
"struct",
|
||||
"typeParameter",
|
||||
"parameter",
|
||||
"variable",
|
||||
"property",
|
||||
"enumMember",
|
||||
"event",
|
||||
"function",
|
||||
"method",
|
||||
"macro",
|
||||
"keyword",
|
||||
"modifier",
|
||||
"comment",
|
||||
"string",
|
||||
"number",
|
||||
"regexp",
|
||||
"operator",
|
||||
"decorator"
|
||||
],
|
||||
"tokenModifiers": [
|
||||
"declaration",
|
||||
"definition",
|
||||
"readonly",
|
||||
"static",
|
||||
"deprecated",
|
||||
"abstract",
|
||||
"async",
|
||||
"modification",
|
||||
"documentation",
|
||||
"defaultLibrary"
|
||||
],
|
||||
"formats": [
|
||||
"relative"
|
||||
],
|
||||
"requests": {
|
||||
"range": true,
|
||||
"full": {
|
||||
"delta": true
|
||||
}
|
||||
},
|
||||
"multilineTokenSupport": false,
|
||||
"overlappingTokenSupport": false,
|
||||
"serverCancelSupport": true,
|
||||
"augmentsSyntaxTokens": false
|
||||
},
|
||||
"linkedEditingRange": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"typeHierarchy": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlineValue": {
|
||||
"dynamicRegistration": true
|
||||
},
|
||||
"inlayHint": {
|
||||
"dynamicRegistration": true,
|
||||
"resolveSupport": {
|
||||
"properties": [
|
||||
"tooltip",
|
||||
"textEdits",
|
||||
"label.tooltip",
|
||||
"label.location",
|
||||
"label.command"
|
||||
]
|
||||
}
|
||||
},
|
||||
"diagnostic": {
|
||||
"dynamicRegistration": true,
|
||||
"relatedDocumentSupport": false
|
||||
}
|
||||
},
|
||||
"general": {
|
||||
"staleRequestSupport": {
|
||||
"cancel": true,
|
||||
"retryOnContentModified": [
|
||||
"textDocument/semanticTokens/full",
|
||||
"textDocument/semanticTokens/range",
|
||||
"textDocument/semanticTokens/full/delta"
|
||||
]
|
||||
},
|
||||
"regularExpressions": {
|
||||
"engine": "ECMAScript",
|
||||
"version": "ES2020"
|
||||
},
|
||||
"markdown": {
|
||||
"parser": "marked",
|
||||
"version": "1.1.0",
|
||||
"allowedTags": [
|
||||
"ul",
|
||||
"li",
|
||||
"p",
|
||||
"code",
|
||||
"blockquote",
|
||||
"ol",
|
||||
"h1",
|
||||
"h2",
|
||||
"h3",
|
||||
"h4",
|
||||
"h5",
|
||||
"h6",
|
||||
"hr",
|
||||
"em",
|
||||
"pre",
|
||||
"table",
|
||||
"thead",
|
||||
"tbody",
|
||||
"tr",
|
||||
"th",
|
||||
"td",
|
||||
"div",
|
||||
"del",
|
||||
"a",
|
||||
"strong",
|
||||
"br",
|
||||
"img",
|
||||
"span"
|
||||
]
|
||||
},
|
||||
"positionEncodings": [
|
||||
"utf-16"
|
||||
]
|
||||
},
|
||||
"notebookDocument": {
|
||||
"synchronization": {
|
||||
"dynamicRegistration": true,
|
||||
"executionSummarySupport": true
|
||||
}
|
||||
},
|
||||
"experimental": {
|
||||
"snippetTextEdit": true,
|
||||
"codeActionGroup": true,
|
||||
"hoverActions": true,
|
||||
"serverStatusNotification": true,
|
||||
"colorDiagnosticOutput": true,
|
||||
"openServerLogs": true,
|
||||
"localDocs": true,
|
||||
"commands": {
|
||||
"commands": [
|
||||
"rust-analyzer.runSingle",
|
||||
"rust-analyzer.debugSingle",
|
||||
"rust-analyzer.showReferences",
|
||||
"rust-analyzer.gotoLocation",
|
||||
"editor.action.triggerParameterHints"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"initializationOptions": {
|
||||
"cargoRunner": null,
|
||||
"runnables": {
|
||||
"extraEnv": null,
|
||||
"problemMatcher": [
|
||||
"$rustc"
|
||||
],
|
||||
"command": null,
|
||||
"extraArgs": []
|
||||
},
|
||||
"statusBar": {
|
||||
"clickAction": "openLogs"
|
||||
},
|
||||
"server": {
|
||||
"path": null,
|
||||
"extraEnv": null
|
||||
},
|
||||
"trace": {
|
||||
"server": "verbose",
|
||||
"extension": false
|
||||
},
|
||||
"debug": {
|
||||
"engine": "auto",
|
||||
"sourceFileMap": {
|
||||
"/rustc/<id>": "${env:USERPROFILE}/.rustup/toolchains/<toolchain-id>/lib/rustlib/src/rust"
|
||||
},
|
||||
"openDebugPane": false,
|
||||
"engineSettings": {}
|
||||
},
|
||||
"restartServerOnConfigChange": false,
|
||||
"typing": {
|
||||
"continueCommentsOnNewline": true,
|
||||
"autoClosingAngleBrackets": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"diagnostics": {
|
||||
"previewRustcOutput": false,
|
||||
"useRustcErrorCode": false,
|
||||
"disabled": [],
|
||||
"enable": true,
|
||||
"experimental": {
|
||||
"enable": false
|
||||
},
|
||||
"remapPrefix": {},
|
||||
"warningsAsHint": [],
|
||||
"warningsAsInfo": []
|
||||
},
|
||||
"discoverProjectRunner": null,
|
||||
"showUnlinkedFileNotification": true,
|
||||
"showDependenciesExplorer": true,
|
||||
"assist": {
|
||||
"emitMustUse": false,
|
||||
"expressionFillDefault": "todo"
|
||||
},
|
||||
"cachePriming": {
|
||||
"enable": true,
|
||||
"numThreads": 0
|
||||
},
|
||||
"cargo": {
|
||||
"autoreload": true,
|
||||
"buildScripts": {
|
||||
"enable": true,
|
||||
"invocationLocation": "workspace",
|
||||
"invocationStrategy": "per_workspace",
|
||||
"overrideCommand": null,
|
||||
"useRustcWrapper": true
|
||||
},
|
||||
"cfgs": {},
|
||||
"extraArgs": [],
|
||||
"extraEnv": {},
|
||||
"features": [],
|
||||
"noDefaultFeatures": false,
|
||||
"sysroot": "discover",
|
||||
"sysrootSrc": null,
|
||||
"target": null,
|
||||
"unsetTest": [
|
||||
"core"
|
||||
]
|
||||
},
|
||||
"checkOnSave": true,
|
||||
"check": {
|
||||
"allTargets": true,
|
||||
"command": "check",
|
||||
"extraArgs": [],
|
||||
"extraEnv": {},
|
||||
"features": null,
|
||||
"ignore": [],
|
||||
"invocationLocation": "workspace",
|
||||
"invocationStrategy": "per_workspace",
|
||||
"noDefaultFeatures": null,
|
||||
"overrideCommand": null,
|
||||
"targets": null
|
||||
},
|
||||
"completion": {
|
||||
"autoimport": {
|
||||
"enable": true
|
||||
},
|
||||
"autoself": {
|
||||
"enable": true
|
||||
},
|
||||
"callable": {
|
||||
"snippets": "fill_arguments"
|
||||
},
|
||||
"fullFunctionSignatures": {
|
||||
"enable": false
|
||||
},
|
||||
"limit": null,
|
||||
"postfix": {
|
||||
"enable": true
|
||||
},
|
||||
"privateEditable": {
|
||||
"enable": false
|
||||
},
|
||||
"snippets": {
|
||||
"custom": {
|
||||
"Arc::new": {
|
||||
"postfix": "arc",
|
||||
"body": "Arc::new(${receiver})",
|
||||
"requires": "std::sync::Arc",
|
||||
"description": "Put the expression into an `Arc`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Rc::new": {
|
||||
"postfix": "rc",
|
||||
"body": "Rc::new(${receiver})",
|
||||
"requires": "std::rc::Rc",
|
||||
"description": "Put the expression into an `Rc`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Box::pin": {
|
||||
"postfix": "pinbox",
|
||||
"body": "Box::pin(${receiver})",
|
||||
"requires": "std::boxed::Box",
|
||||
"description": "Put the expression into a pinned `Box`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Ok": {
|
||||
"postfix": "ok",
|
||||
"body": "Ok(${receiver})",
|
||||
"description": "Wrap the expression in a `Result::Ok`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Err": {
|
||||
"postfix": "err",
|
||||
"body": "Err(${receiver})",
|
||||
"description": "Wrap the expression in a `Result::Err`",
|
||||
"scope": "expr"
|
||||
},
|
||||
"Some": {
|
||||
"postfix": "some",
|
||||
"body": "Some(${receiver})",
|
||||
"description": "Wrap the expression in an `Option::Some`",
|
||||
"scope": "expr"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": {
|
||||
"excludeDirs": [],
|
||||
"watcher": "client"
|
||||
},
|
||||
"highlightRelated": {
|
||||
"breakPoints": {
|
||||
"enable": true
|
||||
},
|
||||
"closureCaptures": {
|
||||
"enable": true
|
||||
},
|
||||
"exitPoints": {
|
||||
"enable": true
|
||||
},
|
||||
"references": {
|
||||
"enable": true
|
||||
},
|
||||
"yieldPoints": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"hover": {
|
||||
"actions": {
|
||||
"debug": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"gotoTypeDef": {
|
||||
"enable": true
|
||||
},
|
||||
"implementations": {
|
||||
"enable": true
|
||||
},
|
||||
"references": {
|
||||
"enable": false
|
||||
},
|
||||
"run": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"documentation": {
|
||||
"enable": true,
|
||||
"keywords": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"enable": true
|
||||
},
|
||||
"memoryLayout": {
|
||||
"alignment": "hexadecimal",
|
||||
"enable": true,
|
||||
"niches": false,
|
||||
"offset": "hexadecimal",
|
||||
"size": "both"
|
||||
}
|
||||
},
|
||||
"imports": {
|
||||
"granularity": {
|
||||
"enforce": false,
|
||||
"group": "crate"
|
||||
},
|
||||
"group": {
|
||||
"enable": true
|
||||
},
|
||||
"merge": {
|
||||
"glob": true
|
||||
},
|
||||
"preferNoStd": false,
|
||||
"preferPrelude": false,
|
||||
"prefix": "plain"
|
||||
},
|
||||
"inlayHints": {
|
||||
"bindingModeHints": {
|
||||
"enable": false
|
||||
},
|
||||
"chainingHints": {
|
||||
"enable": true
|
||||
},
|
||||
"closingBraceHints": {
|
||||
"enable": true,
|
||||
"minLines": 25
|
||||
},
|
||||
"closureCaptureHints": {
|
||||
"enable": false
|
||||
},
|
||||
"closureReturnTypeHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"closureStyle": "impl_fn",
|
||||
"discriminantHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"expressionAdjustmentHints": {
|
||||
"enable": "never",
|
||||
"hideOutsideUnsafe": false,
|
||||
"mode": "prefix"
|
||||
},
|
||||
"lifetimeElisionHints": {
|
||||
"enable": "never",
|
||||
"useParameterNames": false
|
||||
},
|
||||
"maxLength": 25,
|
||||
"parameterHints": {
|
||||
"enable": true
|
||||
},
|
||||
"reborrowHints": {
|
||||
"enable": "never"
|
||||
},
|
||||
"renderColons": true,
|
||||
"typeHints": {
|
||||
"enable": true,
|
||||
"hideClosureInitialization": false,
|
||||
"hideNamedConstructor": false
|
||||
}
|
||||
},
|
||||
"interpret": {
|
||||
"tests": false
|
||||
},
|
||||
"joinLines": {
|
||||
"joinAssignments": true,
|
||||
"joinElseIf": true,
|
||||
"removeTrailingComma": true,
|
||||
"unwrapTrivialBlock": true
|
||||
},
|
||||
"lens": {
|
||||
"debug": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"forceCustomCommands": true,
|
||||
"implementations": {
|
||||
"enable": true
|
||||
},
|
||||
"location": "above_name",
|
||||
"references": {
|
||||
"adt": {
|
||||
"enable": false
|
||||
},
|
||||
"enumVariant": {
|
||||
"enable": false
|
||||
},
|
||||
"method": {
|
||||
"enable": false
|
||||
},
|
||||
"trait": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"run": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"linkedProjects": [],
|
||||
"lru": {
|
||||
"capacity": null,
|
||||
"query": {
|
||||
"capacities": {}
|
||||
}
|
||||
},
|
||||
"notifications": {
|
||||
"cargoTomlNotFound": true
|
||||
},
|
||||
"numThreads": null,
|
||||
"procMacro": {
|
||||
"attributes": {
|
||||
"enable": true
|
||||
},
|
||||
"enable": true,
|
||||
"ignored": {},
|
||||
"server": null
|
||||
},
|
||||
"references": {
|
||||
"excludeImports": false
|
||||
},
|
||||
"rust": {
|
||||
"analyzerTargetDir": null
|
||||
},
|
||||
"rustc": {
|
||||
"source": null
|
||||
},
|
||||
"rustfmt": {
|
||||
"extraArgs": [],
|
||||
"overrideCommand": null,
|
||||
"rangeFormatting": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"semanticHighlighting": {
|
||||
"doc": {
|
||||
"comment": {
|
||||
"inject": {
|
||||
"enable": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"nonStandardTokens": true,
|
||||
"operator": {
|
||||
"enable": true,
|
||||
"specialization": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"punctuation": {
|
||||
"enable": false,
|
||||
"separate": {
|
||||
"macro": {
|
||||
"bang": false
|
||||
}
|
||||
},
|
||||
"specialization": {
|
||||
"enable": false
|
||||
}
|
||||
},
|
||||
"strings": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"signatureInfo": {
|
||||
"detail": "full",
|
||||
"documentation": {
|
||||
"enable": true
|
||||
}
|
||||
},
|
||||
"workspace": {
|
||||
"symbol": {
|
||||
"search": {
|
||||
"kind": "only_types",
|
||||
"limit": 128,
|
||||
"scope": "workspace"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"trace": "verbose",
|
||||
"workspaceFolders": [
|
||||
{
|
||||
"uri": "$uri",
|
||||
"name": "$name"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"_description": "Used to download the runtime dependencies for running typescript-language-server. Obtained from https://github.com/typescript-language-server/typescript-language-server/releases",
|
||||
"runtimeDependencies": [
|
||||
{
|
||||
"id": "typescript",
|
||||
"description": "typescript package for Linux, OSX, and Windows. Both x64 and arm64 are supported.",
|
||||
"command": "npm install --prefix ./ typescript@5.5.4"
|
||||
},
|
||||
{
|
||||
"id": "typescript-language-server",
|
||||
"description": "typescript-language-server package for Linux, OSX, and Windows. Both x64 and arm64 are supported.",
|
||||
"command": "npm install --prefix ./ typescript-language-server@4.3.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
+240
@@ -0,0 +1,240 @@
|
||||
"""
|
||||
Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript.
|
||||
"""
|
||||
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import pathlib
|
||||
import shutil
|
||||
import subprocess
|
||||
import threading
|
||||
from time import sleep
|
||||
|
||||
from overrides import override
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_types import InitializeParams
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
|
||||
from multilspy.multilspy_config import MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from multilspy.multilspy_utils import PlatformId, PlatformUtils
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
# Platform-specific imports
|
||||
if os.name != 'nt': # Unix-like systems
|
||||
import pwd
|
||||
else:
|
||||
# Dummy pwd module for Windows
|
||||
class pwd:
|
||||
@staticmethod
|
||||
def getpwuid(uid):
|
||||
return type('obj', (), {'pw_name': os.environ.get('USERNAME', 'unknown')})()
|
||||
|
||||
|
||||
# Conditionally import pwd module (Unix-only)
|
||||
if not PlatformUtils.get_platform_id().value.startswith("win"):
|
||||
import pwd
|
||||
|
||||
|
||||
class TypeScriptLanguageServer(SolidLanguageServer):
|
||||
"""
|
||||
Provides TypeScript specific instantiation of the LanguageServer class. Contains various configurations and settings specific to TypeScript.
|
||||
"""
|
||||
|
||||
def __init__(self, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str):
|
||||
"""
|
||||
Creates a TypeScriptLanguageServer instance. This class is not meant to be instantiated directly. Use LanguageServer.create() instead.
|
||||
"""
|
||||
ts_lsp_executable_path = self.setup_runtime_dependencies(logger, config)
|
||||
super().__init__(
|
||||
config,
|
||||
logger,
|
||||
repository_root_path,
|
||||
ProcessLaunchInfo(cmd=ts_lsp_executable_path, cwd=repository_root_path),
|
||||
"typescript",
|
||||
)
|
||||
self.server_ready = threading.Event()
|
||||
self.initialize_searcher_command_available = threading.Event()
|
||||
|
||||
@override
|
||||
def is_ignored_dirname(self, dirname: str) -> bool:
|
||||
return super().is_ignored_dirname(dirname) or dirname in [
|
||||
"node_modules",
|
||||
"dist",
|
||||
"build",
|
||||
"coverage",
|
||||
]
|
||||
|
||||
def setup_runtime_dependencies(self, logger: MultilspyLogger, config: MultilspyConfig) -> str:
|
||||
"""
|
||||
Setup runtime dependencies for TypeScript Language Server.
|
||||
"""
|
||||
platform_id = PlatformUtils.get_platform_id()
|
||||
|
||||
valid_platforms = [
|
||||
PlatformId.LINUX_x64,
|
||||
PlatformId.LINUX_arm64,
|
||||
PlatformId.OSX,
|
||||
PlatformId.OSX_x64,
|
||||
PlatformId.OSX_arm64,
|
||||
PlatformId.WIN_x64,
|
||||
PlatformId.WIN_arm64,
|
||||
]
|
||||
assert platform_id in valid_platforms, f"Platform {platform_id} is not supported for multilspy javascript/typescript at the moment"
|
||||
|
||||
with open(os.path.join(os.path.dirname(__file__), "runtime_dependencies.json")) as f:
|
||||
d = json.load(f)
|
||||
del d["_description"]
|
||||
|
||||
runtime_dependencies = d.get("runtimeDependencies", [])
|
||||
tsserver_ls_dir = os.path.join(os.path.dirname(__file__), "static", "ts-lsp")
|
||||
tsserver_executable_path = os.path.join(tsserver_ls_dir, "typescript-language-server")
|
||||
|
||||
# Verify both node and npm are installed
|
||||
is_node_installed = shutil.which('node') is not None
|
||||
assert is_node_installed, "node is not installed or isn't in PATH. Please install NodeJS and try again."
|
||||
is_npm_installed = shutil.which('npm') is not None
|
||||
assert is_npm_installed, "npm is not installed or isn't in PATH. Please install npm and try again."
|
||||
|
||||
# Install typescript and typescript-language-server if not already installed
|
||||
if not os.path.exists(tsserver_ls_dir):
|
||||
os.makedirs(tsserver_ls_dir, exist_ok=True)
|
||||
for dependency in runtime_dependencies:
|
||||
# Windows doesn't support the 'user' parameter and doesn't have pwd module
|
||||
if PlatformUtils.get_platform_id().value.startswith("win"):
|
||||
subprocess.run(
|
||||
dependency["command"],
|
||||
shell=True,
|
||||
check=True,
|
||||
cwd=tsserver_ls_dir,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
else:
|
||||
# On Unix-like systems, run as non-root user
|
||||
user = pwd.getpwuid(os.getuid()).pw_name
|
||||
subprocess.run(
|
||||
dependency["command"],
|
||||
shell=True,
|
||||
check=True,
|
||||
user=user,
|
||||
cwd=tsserver_ls_dir,
|
||||
stdout=subprocess.DEVNULL,
|
||||
stderr=subprocess.DEVNULL
|
||||
)
|
||||
|
||||
tsserver_executable_path = os.path.join(tsserver_ls_dir, "node_modules", ".bin", "typescript-language-server")
|
||||
|
||||
assert os.path.exists(tsserver_executable_path), "typescript-language-server executable not found. Please install typescript-language-server and try again."
|
||||
return f"{tsserver_executable_path} --stdio"
|
||||
|
||||
def _get_initialize_params(self, repository_absolute_path: str) -> InitializeParams:
|
||||
"""
|
||||
Returns the initialize params for the TypeScript Language Server.
|
||||
"""
|
||||
with open(os.path.join(os.path.dirname(__file__), "initialize_params.json")) as f:
|
||||
d = json.load(f)
|
||||
|
||||
del d["_description"]
|
||||
|
||||
d["processId"] = os.getpid()
|
||||
assert d["rootPath"] == "$rootPath"
|
||||
d["rootPath"] = repository_absolute_path
|
||||
|
||||
assert d["rootUri"] == "$rootUri"
|
||||
d["rootUri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["uri"] == "$uri"
|
||||
d["workspaceFolders"][0]["uri"] = pathlib.Path(repository_absolute_path).as_uri()
|
||||
|
||||
assert d["workspaceFolders"][0]["name"] == "$name"
|
||||
d["workspaceFolders"][0]["name"] = os.path.basename(repository_absolute_path)
|
||||
|
||||
return d
|
||||
|
||||
def _start_server(self):
|
||||
"""
|
||||
Starts the TypeScript Language Server, waits for the server to be ready and yields the LanguageServer instance.
|
||||
|
||||
Usage:
|
||||
```
|
||||
async with lsp.start_server():
|
||||
# LanguageServer has been initialized and ready to serve requests
|
||||
await lsp.request_definition(...)
|
||||
await lsp.request_references(...)
|
||||
# Shutdown the LanguageServer on exit from scope
|
||||
# LanguageServer has been shutdown
|
||||
"""
|
||||
|
||||
def register_capability_handler(params):
|
||||
assert "registrations" in params
|
||||
for registration in params["registrations"]:
|
||||
if registration["method"] == "workspace/executeCommand":
|
||||
self.initialize_searcher_command_available.set()
|
||||
# TypeScript doesn't have a direct equivalent to resolve_main_method
|
||||
# You might want to set a different flag or remove this line
|
||||
# self.resolve_main_method_available.set()
|
||||
return
|
||||
|
||||
def execute_client_command_handler(params):
|
||||
return []
|
||||
|
||||
def do_nothing(params):
|
||||
return
|
||||
|
||||
def window_log_message(msg):
|
||||
self.logger.log(f"LSP: window/logMessage: {msg}", logging.INFO)
|
||||
|
||||
|
||||
def check_experimental_status(params):
|
||||
"""
|
||||
Also listen for experimental/serverStatus as a backup signal
|
||||
"""
|
||||
if params.get("quiescent") == True:
|
||||
self.server_ready.set()
|
||||
self.completions_available.set()
|
||||
|
||||
self.server.on_request("client/registerCapability", register_capability_handler)
|
||||
self.server.on_notification("window/logMessage", window_log_message)
|
||||
self.server.on_request("workspace/executeClientCommand", execute_client_command_handler)
|
||||
self.server.on_notification("$/progress", do_nothing)
|
||||
self.server.on_notification("textDocument/publishDiagnostics", do_nothing)
|
||||
self.server.on_notification("experimental/serverStatus", check_experimental_status)
|
||||
|
||||
self.logger.log("Starting TypeScript server process", logging.INFO)
|
||||
self.server.start()
|
||||
initialize_params = self._get_initialize_params(self.repository_root_path)
|
||||
|
||||
self.logger.log(
|
||||
"Sending initialize request from LSP client to LSP server and awaiting response",
|
||||
logging.INFO,
|
||||
)
|
||||
init_response = self.server.send.initialize(initialize_params)
|
||||
|
||||
# TypeScript-specific capability checks
|
||||
assert init_response["capabilities"]["textDocumentSync"] == 2
|
||||
assert "completionProvider" in init_response["capabilities"]
|
||||
assert init_response["capabilities"]["completionProvider"] == {
|
||||
"triggerCharacters": ['.', '"', "'", '/', '@', '<'],
|
||||
"resolveProvider": True
|
||||
}
|
||||
|
||||
self.server.notify.initialized({})
|
||||
if self.server_ready.wait(timeout=1.0):
|
||||
self.logger.log("TypeScript server is ready", logging.INFO)
|
||||
else:
|
||||
self.logger.log("Timeout waiting for TypeScript server to become ready, proceeding anyway", logging.INFO)
|
||||
# Fallback: assume server is ready after timeout
|
||||
self.server_ready.set()
|
||||
self.completions_available.set()
|
||||
|
||||
@override
|
||||
# For some reason, the LS may need longer to process this, so we just retry
|
||||
def _send_references_request(self, relative_file_path: str, line: int, column: int):
|
||||
# TODO: The LS doesn't return references contained in other files if it doesn't sleep. This is
|
||||
# despite the LS having processed requests already. I don't know what causes this, but sleeping
|
||||
# one second helps. It may be that sleeping only once is enough but that's hard to reliably test.
|
||||
# It may be that even this 1sec is not enough in larger TS projects, at some point we should find what
|
||||
# causes this and solve it.
|
||||
sleep(1)
|
||||
return super()._send_references_request(relative_file_path, line, column)
|
||||
+1635
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,513 @@
|
||||
import asyncio
|
||||
import json
|
||||
import logging
|
||||
import os
|
||||
import subprocess
|
||||
import threading
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from queue import Queue
|
||||
from typing import Any, Callable, Dict, Optional
|
||||
|
||||
import psutil
|
||||
|
||||
from multilspy.lsp_protocol_handler.lsp_requests import LspNotification
|
||||
from multilspy.lsp_protocol_handler.lsp_types import ErrorCodes
|
||||
from multilspy.lsp_protocol_handler.server import ProcessLaunchInfo, StringDict, content_length, ENCODING, Error, MessageType, \
|
||||
make_notification, PayloadLike, make_response, make_error_response, make_request, create_message
|
||||
from multilspy.multilspy_exceptions import MultilspyException
|
||||
from solidlsp.lsp_request import SolidLspRequest
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Request:
|
||||
|
||||
@dataclass
|
||||
class Result:
|
||||
payload: Optional[PayloadLike] = None
|
||||
error: Optional[Error] = None
|
||||
|
||||
def is_error(self) -> bool:
|
||||
return self.error is not None
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._result_queue = Queue()
|
||||
|
||||
def on_result(self, params: PayloadLike) -> None:
|
||||
self._result_queue.put(Request.Result(payload=params))
|
||||
|
||||
def on_error(self, err: Error) -> None:
|
||||
self._result_queue.put(Request.Result(error=err))
|
||||
|
||||
def get_result(self) -> Result:
|
||||
# TODO could add timeout
|
||||
return self._result_queue.get()
|
||||
|
||||
|
||||
class SolidLanguageServerHandler:
|
||||
"""
|
||||
This class provides the implementation of Python client for the Language Server Protocol.
|
||||
A class that launches the language server and communicates with it
|
||||
using the Language Server Protocol (LSP).
|
||||
|
||||
It provides methods for sending requests, responses, and notifications to the server
|
||||
and for registering handlers for requests and notifications from the server.
|
||||
|
||||
Uses JSON-RPC 2.0 for communication with the server over stdin/stdout.
|
||||
|
||||
Attributes:
|
||||
send: A LspRequest object that can be used to send requests to the server and
|
||||
await for the responses.
|
||||
notify: A LspNotification object that can be used to send notifications to the server.
|
||||
cmd: A string that represents the command to launch the language server process.
|
||||
process: A subprocess.Popen object that represents the language server process.
|
||||
_received_shutdown: A boolean flag that indicates whether the client has received
|
||||
a shutdown request from the server.
|
||||
request_id: An integer that represents the next available request id for the client.
|
||||
_response_handlers: A dictionary that maps request ids to Request objects that
|
||||
store the results or errors of the requests.
|
||||
on_request_handlers: A dictionary that maps method names to callback functions
|
||||
that handle requests from the server.
|
||||
on_notification_handlers: A dictionary that maps method names to callback functions
|
||||
that handle notifications from the server.
|
||||
logger: An optional function that takes two strings (source and destination) and
|
||||
a payload dictionary, and logs the communication between the client and the server.
|
||||
tasks: A dictionary that maps task ids to asyncio.Task objects that represent
|
||||
the asynchronous tasks created by the handler.
|
||||
task_counter: An integer that represents the next available task id for the handler.
|
||||
loop: An asyncio.AbstractEventLoop object that represents the event loop used by the handler.
|
||||
start_independent_lsp_process: An optional boolean flag that indicates whether to start the
|
||||
language server process in an independent process group. Default is `True`. Setting it to
|
||||
`False` means that the language server process will be in the same process group as the
|
||||
the current process, and any SIGINT and SIGTERM signals will be sent to both processes.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
process_launch_info: ProcessLaunchInfo,
|
||||
logger: Optional[Callable[[str, str, StringDict | str], None]] = None,
|
||||
start_independent_lsp_process=True,
|
||||
) -> None:
|
||||
"""
|
||||
Params:
|
||||
cmd: A string that represents the command to launch the language server process.
|
||||
logger: An optional function that takes two strings (source and destination) and
|
||||
a payload dictionary, and logs the communication between the client and the server.
|
||||
"""
|
||||
self.send = SolidLspRequest(self.send_request)
|
||||
self.notify = LspNotification(self.send_notification)
|
||||
|
||||
self.process_launch_info = process_launch_info
|
||||
self.process = None
|
||||
self._received_shutdown = False
|
||||
|
||||
self.request_id = 1
|
||||
self._response_handlers: Dict[Any, Request] = {}
|
||||
self.on_request_handlers = {}
|
||||
self.on_notification_handlers = {}
|
||||
self.logger = logger
|
||||
self.tasks = {}
|
||||
self.task_counter = 0
|
||||
self.loop = None
|
||||
self.start_independent_lsp_process = start_independent_lsp_process
|
||||
|
||||
# Add thread locks for shared resources to prevent race conditions
|
||||
self._stdin_lock = threading.Lock()
|
||||
self._request_id_lock = threading.Lock()
|
||||
self._response_handlers_lock = threading.Lock()
|
||||
self._tasks_lock = threading.Lock()
|
||||
|
||||
def is_running(self) -> bool:
|
||||
"""
|
||||
Checks if the language server process is currently running.
|
||||
"""
|
||||
return self.process is not None and self.process.returncode is None
|
||||
|
||||
def start(self) -> None:
|
||||
"""
|
||||
Starts the language server process and creates a task to continuously read from its stdout to handle communications
|
||||
from the server to the client
|
||||
"""
|
||||
child_proc_env = os.environ.copy()
|
||||
child_proc_env.update(self.process_launch_info.env)
|
||||
|
||||
log.info("Starting language server process via command: %s", self.process_launch_info.cmd)
|
||||
self.process = subprocess.Popen(
|
||||
self.process_launch_info.cmd,
|
||||
stdout=subprocess.PIPE,
|
||||
stdin=subprocess.PIPE,
|
||||
stderr=subprocess.PIPE,
|
||||
env=child_proc_env,
|
||||
cwd=self.process_launch_info.cwd,
|
||||
start_new_session=self.start_independent_lsp_process,
|
||||
shell=True
|
||||
)
|
||||
|
||||
# Check if process terminated immediately
|
||||
if self.process.returncode is not None:
|
||||
log.error("Language server has already terminated/could not be started")
|
||||
# Process has already terminated
|
||||
stderr_data = self.process.stderr.read()
|
||||
error_message = stderr_data.decode('utf-8', errors='replace')
|
||||
raise RuntimeError(f"Process terminated immediately with code {self.process.returncode}. Error: {error_message}")
|
||||
|
||||
# start threads to read stdout and stderr of the process
|
||||
threading.Thread(
|
||||
target=self.run_forever,
|
||||
name="LSP stdout reader",
|
||||
daemon=True,
|
||||
).start()
|
||||
threading.Thread(
|
||||
target=self.run_forever_stderr,
|
||||
name="LSP stderr reader",
|
||||
daemon=True,
|
||||
).start()
|
||||
|
||||
def stop(self) -> None:
|
||||
"""
|
||||
Sends the terminate signal to the language server process and waits for it to exit, with a timeout, killing it if necessary
|
||||
"""
|
||||
process = self.process
|
||||
self.process = None
|
||||
if process:
|
||||
self._cleanup_process(process)
|
||||
|
||||
def _cleanup_process(self, process):
|
||||
"""Clean up a process: close stdin, terminate/kill process, close stdout/stderr."""
|
||||
# Close stdin first to prevent deadlocks
|
||||
# See: https://bugs.python.org/issue35539
|
||||
self._safely_close_pipe(process.stdin)
|
||||
|
||||
# Terminate/kill the process if it's still running
|
||||
if process.returncode is None:
|
||||
self._terminate_or_kill_process(process)
|
||||
|
||||
# Close stdout and stderr pipes after process has exited
|
||||
# This is essential to prevent "I/O operation on closed pipe" errors and
|
||||
# "Event loop is closed" errors during garbage collection
|
||||
# See: https://bugs.python.org/issue41320 and https://github.com/python/cpython/issues/88050
|
||||
self._safely_close_pipe(process.stdout)
|
||||
self._safely_close_pipe(process.stderr)
|
||||
|
||||
def _safely_close_pipe(self, pipe):
|
||||
"""Safely close a pipe, ignoring any exceptions."""
|
||||
if pipe:
|
||||
try:
|
||||
pipe.close()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _terminate_or_kill_process(self, process):
|
||||
"""Try to terminate the process gracefully, then forcefully if necessary."""
|
||||
# First try to terminate the process tree gracefully
|
||||
self._signal_process_tree(process, terminate=True)
|
||||
|
||||
# TODO
|
||||
"""
|
||||
# Wait for the process to exit (with timeout)
|
||||
try:
|
||||
asyncio.wait_for(process.wait(), timeout=10)
|
||||
except (asyncio.TimeoutError, Exception):
|
||||
# If termination failed, forcefully kill the process tree
|
||||
self._signal_process_tree(process, terminate=False)
|
||||
try:
|
||||
# Give it one more chance to exit
|
||||
await asyncio.wait_for(process.wait(), timeout=2)
|
||||
except Exception:
|
||||
pass
|
||||
"""
|
||||
|
||||
def _signal_process_tree(self, process, terminate=True):
|
||||
"""Send signal (terminate or kill) to the process and all its children."""
|
||||
signal_method = "terminate" if terminate else "kill"
|
||||
|
||||
# Try to get the parent process
|
||||
parent = None
|
||||
try:
|
||||
parent = psutil.Process(process.pid)
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception):
|
||||
pass
|
||||
|
||||
# If we have the parent process and it's running, signal the entire tree
|
||||
if parent and parent.is_running():
|
||||
# Signal children first
|
||||
for child in parent.children(recursive=True):
|
||||
try:
|
||||
getattr(child, signal_method)()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception):
|
||||
pass
|
||||
|
||||
# Then signal the parent
|
||||
try:
|
||||
getattr(parent, signal_method)()
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied, Exception):
|
||||
pass
|
||||
else:
|
||||
# Fall back to direct process signaling
|
||||
try:
|
||||
getattr(process, signal_method)()
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""
|
||||
Perform the shutdown sequence for the client, including sending the shutdown request to the server and notifying it of exit
|
||||
"""
|
||||
self._log("Sending shutdown request to server")
|
||||
self.send.shutdown()
|
||||
self._log("Received shutdown response from server")
|
||||
self._received_shutdown = True
|
||||
self._log("Sending exit notification to server")
|
||||
self.notify.exit()
|
||||
self._log("Sent exit notification to server")
|
||||
# TODO
|
||||
"""
|
||||
if self.process and self.process.stdout:
|
||||
self.process.stdout.set_exception(StopLoopException())
|
||||
# This yields the control to the event loop to allow the exception to be handled
|
||||
# in the run_forever and run_forever_stderr methods
|
||||
await asyncio.sleep(0)
|
||||
"""
|
||||
|
||||
def _log(self, message: str | StringDict) -> None:
|
||||
"""
|
||||
Create a log message
|
||||
"""
|
||||
if self.logger is not None:
|
||||
self.logger("client", "logger", message)
|
||||
|
||||
@staticmethod
|
||||
def _read_bytes_from_process(process, stream, num_bytes):
|
||||
"""Read exactly num_bytes from process stdout"""
|
||||
if process.poll() is not None:
|
||||
# Process has terminated, check if we can still read
|
||||
pass
|
||||
|
||||
data = b''
|
||||
while len(data) < num_bytes:
|
||||
chunk = stream.read(num_bytes - len(data))
|
||||
if not chunk:
|
||||
if process.poll() is not None:
|
||||
raise EOFError(f"Process terminated. Expected {num_bytes} bytes, got {len(data)}")
|
||||
# Process still running but no data available yet
|
||||
time.sleep(0.01) # Small delay
|
||||
continue
|
||||
data += chunk
|
||||
return data
|
||||
|
||||
def run_forever(self) -> bool:
|
||||
"""
|
||||
Continuously read from the language server process stdout and handle the messages
|
||||
invoking the registered response and notification handlers
|
||||
"""
|
||||
try:
|
||||
while self.process and self.process.stdout and self.process.stdout.readable():
|
||||
line = self.process.stdout.readline()
|
||||
if not line:
|
||||
continue
|
||||
try:
|
||||
num_bytes = content_length(line)
|
||||
except ValueError:
|
||||
continue
|
||||
if num_bytes is None:
|
||||
continue
|
||||
while line and line.strip():
|
||||
line = self.process.stdout.readline()
|
||||
if not line:
|
||||
continue
|
||||
body = self._read_bytes_from_process(self.process, self.process.stdout, num_bytes)
|
||||
|
||||
self._handle_body(body)
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
pass
|
||||
return self._received_shutdown
|
||||
|
||||
def run_forever_stderr(self) -> None:
|
||||
"""
|
||||
Continuously read from the language server process stderr and log the messages
|
||||
"""
|
||||
try:
|
||||
while self.process and self.process.stderr and self.process.stderr.readable():
|
||||
line = self.process.stderr.readline()
|
||||
if not line:
|
||||
continue
|
||||
self._log("LSP stderr: " + line.decode(ENCODING, errors='replace'))
|
||||
except (BrokenPipeError, ConnectionResetError):
|
||||
pass
|
||||
|
||||
def _handle_body(self, body: bytes) -> None:
|
||||
"""
|
||||
Parse the body text received from the language server process and invoke the appropriate handler
|
||||
"""
|
||||
try:
|
||||
self._receive_payload(json.loads(body))
|
||||
except IOError as ex:
|
||||
self._log(f"malformed {ENCODING}: {ex}")
|
||||
except UnicodeDecodeError as ex:
|
||||
self._log(f"malformed {ENCODING}: {ex}")
|
||||
except json.JSONDecodeError as ex:
|
||||
self._log(f"malformed JSON: {ex}")
|
||||
|
||||
def _receive_payload(self, payload: StringDict) -> None:
|
||||
"""
|
||||
Determine if the payload received from server is for a request, response, or notification and invoke the appropriate handler
|
||||
"""
|
||||
if self.logger:
|
||||
self.logger("server", "client", payload)
|
||||
try:
|
||||
if "method" in payload:
|
||||
if "id" in payload:
|
||||
self._request_handler(payload)
|
||||
else:
|
||||
self._notification_handler(payload)
|
||||
elif "id" in payload:
|
||||
self._response_handler(payload)
|
||||
else:
|
||||
self._log(f"Unknown payload type: {payload}")
|
||||
except Exception as err:
|
||||
self._log(f"Error handling server payload: {err}")
|
||||
|
||||
def send_notification(self, method: str, params: Optional[dict] = None) -> None:
|
||||
"""
|
||||
Send notification pertaining to the given method to the server with the given parameters
|
||||
"""
|
||||
self._send_payload(make_notification(method, params))
|
||||
|
||||
def send_response(self, request_id: Any, params: PayloadLike) -> None:
|
||||
"""
|
||||
Send response to the given request id to the server with the given parameters
|
||||
"""
|
||||
self._send_payload(make_response(request_id, params))
|
||||
|
||||
def send_error_response(self, request_id: Any, err: Error) -> None:
|
||||
"""
|
||||
Send error response to the given request id to the server with the given error
|
||||
"""
|
||||
# Use lock to prevent race conditions on tasks and task_counter
|
||||
self._send_payload(make_error_response(request_id, err))
|
||||
|
||||
def send_request(self, method: str, params: Optional[dict] = None) -> PayloadLike:
|
||||
"""
|
||||
Send request to the server, register the request id, and wait for the response
|
||||
"""
|
||||
request = Request()
|
||||
|
||||
# Use lock to prevent race conditions on request_id and _response_handlers
|
||||
with self._request_id_lock:
|
||||
request_id = self.request_id
|
||||
self.request_id += 1
|
||||
|
||||
with self._response_handlers_lock:
|
||||
self._response_handlers[request_id] = request
|
||||
|
||||
self._send_payload(make_request(method, request_id, params))
|
||||
|
||||
self._log(f"Waiting for response to request {method} with params:\n{params}")
|
||||
result = request.get_result()
|
||||
|
||||
self._log(f"Processing result")
|
||||
if result.is_error():
|
||||
raise MultilspyException(f"Could not process request {method} with params:\n{params}.\n Language server error: {result.error}") from result.error
|
||||
|
||||
self._log(f"Returning non-error result, which is:\n{result.payload}")
|
||||
return result.payload
|
||||
|
||||
def _send_payload(self, payload: StringDict) -> None:
|
||||
"""
|
||||
Send the payload to the server by writing to its stdin asynchronously.
|
||||
"""
|
||||
if not self.process or not self.process.stdin:
|
||||
return
|
||||
self._log(payload)
|
||||
msg = create_message(payload)
|
||||
|
||||
# Use lock to prevent concurrent writes to stdin that cause buffer corruption
|
||||
with self._stdin_lock:
|
||||
try:
|
||||
self.process.stdin.writelines(msg)
|
||||
self.process.stdin.flush()
|
||||
except (BrokenPipeError, ConnectionResetError, OSError) as e:
|
||||
# Log the error but don't raise to prevent cascading failures
|
||||
if self.logger:
|
||||
self.logger("client", "logger", f"Failed to write to stdin: {e}")
|
||||
return
|
||||
|
||||
def on_request(self, method: str, cb) -> None:
|
||||
"""
|
||||
Register the callback function to handle requests from the server to the client for the given method
|
||||
"""
|
||||
self.on_request_handlers[method] = cb
|
||||
|
||||
def on_notification(self, method: str, cb) -> None:
|
||||
"""
|
||||
Register the callback function to handle notifications from the server to the client for the given method
|
||||
"""
|
||||
self.on_notification_handlers[method] = cb
|
||||
|
||||
def _response_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
Handle the response received from the server for a request, using the id to determine the request
|
||||
"""
|
||||
with self._response_handlers_lock:
|
||||
request = self._response_handlers.pop(response["id"])
|
||||
|
||||
if "result" in response and "error" not in response:
|
||||
request.on_result(response["result"])
|
||||
elif "result" not in response and "error" in response:
|
||||
request.on_error(Error.from_lsp(response["error"]))
|
||||
else:
|
||||
request.on_error(Error(ErrorCodes.InvalidRequest, ""))
|
||||
|
||||
def _request_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
Handle the request received from the server: call the appropriate callback function and return the result
|
||||
"""
|
||||
method = response.get("method", "")
|
||||
params = response.get("params")
|
||||
request_id = response.get("id")
|
||||
handler = self.on_request_handlers.get(method)
|
||||
if not handler:
|
||||
self.send_error_response(
|
||||
request_id,
|
||||
Error(
|
||||
ErrorCodes.MethodNotFound,
|
||||
"method '{}' not handled on client.".format(method),
|
||||
),
|
||||
)
|
||||
return
|
||||
try:
|
||||
self.send_response(request_id, handler(params))
|
||||
except Error as ex:
|
||||
self.send_error_response(request_id, ex)
|
||||
except Exception as ex:
|
||||
self.send_error_response(request_id, Error(ErrorCodes.InternalError, str(ex)))
|
||||
|
||||
def _notification_handler(self, response: StringDict) -> None:
|
||||
"""
|
||||
Handle the notification received from the server: call the appropriate callback function
|
||||
"""
|
||||
method = response.get("method", "")
|
||||
params = response.get("params")
|
||||
handler = self.on_notification_handlers.get(method)
|
||||
if not handler:
|
||||
self._log(f"unhandled {method}")
|
||||
return
|
||||
try:
|
||||
handler(params)
|
||||
except asyncio.CancelledError:
|
||||
return
|
||||
except Exception as ex:
|
||||
if (not self._received_shutdown) and self.logger:
|
||||
self.logger(
|
||||
"client",
|
||||
"logger",
|
||||
str(
|
||||
{
|
||||
"type": MessageType.error,
|
||||
"message": str(ex),
|
||||
"method": method,
|
||||
"params": params,
|
||||
}
|
||||
),
|
||||
)
|
||||
@@ -0,0 +1,435 @@
|
||||
from typing import List, Union
|
||||
from multilspy.lsp_protocol_handler import lsp_types
|
||||
|
||||
|
||||
class SolidLspRequest:
|
||||
def __init__(self, send_request):
|
||||
self.send_request = send_request
|
||||
|
||||
def implementation(
|
||||
self, params: lsp_types.ImplementationParams
|
||||
) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]:
|
||||
"""A request to resolve the implementation locations of a symbol at a given text
|
||||
document position. The request's parameter is of type [TextDocumentPositionParams]
|
||||
(#TextDocumentPositionParams) the response is of type {@link Definition} or a
|
||||
Thenable that resolves to such."""
|
||||
return self.send_request("textDocument/implementation", params)
|
||||
|
||||
def type_definition(
|
||||
self, params: lsp_types.TypeDefinitionParams
|
||||
) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]:
|
||||
"""A request to resolve the type definition locations of a symbol at a given text
|
||||
document position. The request's parameter is of type [TextDocumentPositionParams]
|
||||
(#TextDocumentPositionParams) the response is of type {@link Definition} or a
|
||||
Thenable that resolves to such."""
|
||||
return self.send_request("textDocument/typeDefinition", params)
|
||||
|
||||
def document_color(
|
||||
self, params: lsp_types.DocumentColorParams
|
||||
) -> List["lsp_types.ColorInformation"]:
|
||||
"""A request to list all color symbols found in a given text document. The request's
|
||||
parameter is of type {@link DocumentColorParams} the
|
||||
response is of type {@link ColorInformation ColorInformation[]} or a Thenable
|
||||
that resolves to such."""
|
||||
return self.send_request("textDocument/documentColor", params)
|
||||
|
||||
def color_presentation(
|
||||
self, params: lsp_types.ColorPresentationParams
|
||||
) -> List["lsp_types.ColorPresentation"]:
|
||||
"""A request to list all presentation for a color. The request's
|
||||
parameter is of type {@link ColorPresentationParams} the
|
||||
response is of type {@link ColorInformation ColorInformation[]} or a Thenable
|
||||
that resolves to such."""
|
||||
return self.send_request("textDocument/colorPresentation", params)
|
||||
|
||||
def folding_range(
|
||||
self, params: lsp_types.FoldingRangeParams
|
||||
) -> Union[List["lsp_types.FoldingRange"], None]:
|
||||
"""A request to provide folding ranges in a document. The request's
|
||||
parameter is of type {@link FoldingRangeParams}, the
|
||||
response is of type {@link FoldingRangeList} or a Thenable
|
||||
that resolves to such."""
|
||||
return self.send_request("textDocument/foldingRange", params)
|
||||
|
||||
def declaration(
|
||||
self, params: lsp_types.DeclarationParams
|
||||
) -> Union["lsp_types.Declaration", List["lsp_types.LocationLink"], None]:
|
||||
"""A request to resolve the type definition locations of a symbol at a given text
|
||||
document position. The request's parameter is of type [TextDocumentPositionParams]
|
||||
(#TextDocumentPositionParams) the response is of type {@link Declaration}
|
||||
or a typed array of {@link DeclarationLink} or a Thenable that resolves
|
||||
to such."""
|
||||
return self.send_request("textDocument/declaration", params)
|
||||
|
||||
def selection_range(
|
||||
self, params: lsp_types.SelectionRangeParams
|
||||
) -> Union[List["lsp_types.SelectionRange"], None]:
|
||||
"""A request to provide selection ranges in a document. The request's
|
||||
parameter is of type {@link SelectionRangeParams}, the
|
||||
response is of type {@link SelectionRange SelectionRange[]} or a Thenable
|
||||
that resolves to such."""
|
||||
return self.send_request("textDocument/selectionRange", params)
|
||||
|
||||
def prepare_call_hierarchy(
|
||||
self, params: lsp_types.CallHierarchyPrepareParams
|
||||
) -> Union[List["lsp_types.CallHierarchyItem"], None]:
|
||||
"""A request to result a `CallHierarchyItem` in a document at a given position.
|
||||
Can be used as an input to an incoming or outgoing call hierarchy.
|
||||
|
||||
@since 3.16.0"""
|
||||
return self.send_request("textDocument/prepareCallHierarchy", params)
|
||||
|
||||
def incoming_calls(
|
||||
self, params: lsp_types.CallHierarchyIncomingCallsParams
|
||||
) -> Union[List["lsp_types.CallHierarchyIncomingCall"], None]:
|
||||
"""A request to resolve the incoming calls for a given `CallHierarchyItem`.
|
||||
|
||||
@since 3.16.0"""
|
||||
return self.send_request("callHierarchy/incomingCalls", params)
|
||||
|
||||
def outgoing_calls(
|
||||
self, params: lsp_types.CallHierarchyOutgoingCallsParams
|
||||
) -> Union[List["lsp_types.CallHierarchyOutgoingCall"], None]:
|
||||
"""A request to resolve the outgoing calls for a given `CallHierarchyItem`.
|
||||
|
||||
@since 3.16.0"""
|
||||
return self.send_request("callHierarchy/outgoingCalls", params)
|
||||
|
||||
def semantic_tokens_full(
|
||||
self, params: lsp_types.SemanticTokensParams
|
||||
) -> Union["lsp_types.SemanticTokens", None]:
|
||||
"""@since 3.16.0"""
|
||||
return self.send_request("textDocument/semanticTokens/full", params)
|
||||
|
||||
def semantic_tokens_delta(
|
||||
self, params: lsp_types.SemanticTokensDeltaParams
|
||||
) -> Union["lsp_types.SemanticTokens", "lsp_types.SemanticTokensDelta", None]:
|
||||
"""@since 3.16.0"""
|
||||
return self.send_request("textDocument/semanticTokens/full/delta", params)
|
||||
|
||||
def semantic_tokens_range(
|
||||
self, params: lsp_types.SemanticTokensRangeParams
|
||||
) -> Union["lsp_types.SemanticTokens", None]:
|
||||
"""@since 3.16.0"""
|
||||
return self.send_request("textDocument/semanticTokens/range", params)
|
||||
|
||||
def linked_editing_range(
|
||||
self, params: lsp_types.LinkedEditingRangeParams
|
||||
) -> Union["lsp_types.LinkedEditingRanges", None]:
|
||||
"""A request to provide ranges that can be edited together.
|
||||
|
||||
@since 3.16.0"""
|
||||
return self.send_request("textDocument/linkedEditingRange", params)
|
||||
|
||||
def will_create_files(
|
||||
self, params: lsp_types.CreateFilesParams
|
||||
) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""The will create files request is sent from the client to the server before files are actually
|
||||
created as long as the creation is triggered from within the client.
|
||||
|
||||
@since 3.16.0"""
|
||||
return self.send_request("workspace/willCreateFiles", params)
|
||||
|
||||
def will_rename_files(
|
||||
self, params: lsp_types.RenameFilesParams
|
||||
) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""The will rename files request is sent from the client to the server before files are actually
|
||||
renamed as long as the rename is triggered from within the client.
|
||||
|
||||
@since 3.16.0"""
|
||||
return self.send_request("workspace/willRenameFiles", params)
|
||||
|
||||
def will_delete_files(
|
||||
self, params: lsp_types.DeleteFilesParams
|
||||
) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""The did delete files notification is sent from the client to the server when
|
||||
files were deleted from within the client.
|
||||
|
||||
@since 3.16.0"""
|
||||
return self.send_request("workspace/willDeleteFiles", params)
|
||||
|
||||
def moniker(
|
||||
self, params: lsp_types.MonikerParams
|
||||
) -> Union[List["lsp_types.Moniker"], None]:
|
||||
"""A request to get the moniker of a symbol at a given text document position.
|
||||
The request parameter is of type {@link TextDocumentPositionParams}.
|
||||
The response is of type {@link Moniker Moniker[]} or `null`."""
|
||||
return self.send_request("textDocument/moniker", params)
|
||||
|
||||
def prepare_type_hierarchy(
|
||||
self, params: lsp_types.TypeHierarchyPrepareParams
|
||||
) -> Union[List["lsp_types.TypeHierarchyItem"], None]:
|
||||
"""A request to result a `TypeHierarchyItem` in a document at a given position.
|
||||
Can be used as an input to a subtypes or supertypes type hierarchy.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("textDocument/prepareTypeHierarchy", params)
|
||||
|
||||
def type_hierarchy_supertypes(
|
||||
self, params: lsp_types.TypeHierarchySupertypesParams
|
||||
) -> Union[List["lsp_types.TypeHierarchyItem"], None]:
|
||||
"""A request to resolve the supertypes for a given `TypeHierarchyItem`.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("typeHierarchy/supertypes", params)
|
||||
|
||||
def type_hierarchy_subtypes(
|
||||
self, params: lsp_types.TypeHierarchySubtypesParams
|
||||
) -> Union[List["lsp_types.TypeHierarchyItem"], None]:
|
||||
"""A request to resolve the subtypes for a given `TypeHierarchyItem`.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("typeHierarchy/subtypes", params)
|
||||
|
||||
def inline_value(
|
||||
self, params: lsp_types.InlineValueParams
|
||||
) -> Union[List["lsp_types.InlineValue"], None]:
|
||||
"""A request to provide inline values in a document. The request's parameter is of
|
||||
type {@link InlineValueParams}, the response is of type
|
||||
{@link InlineValue InlineValue[]} or a Thenable that resolves to such.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("textDocument/inlineValue", params)
|
||||
|
||||
def inlay_hint(
|
||||
self, params: lsp_types.InlayHintParams
|
||||
) -> Union[List["lsp_types.InlayHint"], None]:
|
||||
"""A request to provide inlay hints in a document. The request's parameter is of
|
||||
type {@link InlayHintsParams}, the response is of type
|
||||
{@link InlayHint InlayHint[]} or a Thenable that resolves to such.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("textDocument/inlayHint", params)
|
||||
|
||||
def resolve_inlay_hint(
|
||||
self, params: lsp_types.InlayHint
|
||||
) -> "lsp_types.InlayHint":
|
||||
"""A request to resolve additional properties for an inlay hint.
|
||||
The request's parameter is of type {@link InlayHint}, the response is
|
||||
of type {@link InlayHint} or a Thenable that resolves to such.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("inlayHint/resolve", params)
|
||||
|
||||
def text_document_diagnostic(
|
||||
self, params: lsp_types.DocumentDiagnosticParams
|
||||
) -> "lsp_types.DocumentDiagnosticReport":
|
||||
"""The document diagnostic request definition.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("textDocument/diagnostic", params)
|
||||
|
||||
def workspace_diagnostic(
|
||||
self, params: lsp_types.WorkspaceDiagnosticParams
|
||||
) -> "lsp_types.WorkspaceDiagnosticReport":
|
||||
"""The workspace diagnostic request definition.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("workspace/diagnostic", params)
|
||||
|
||||
def initialize(
|
||||
self, params: lsp_types.InitializeParams
|
||||
) -> "lsp_types.InitializeResult":
|
||||
"""The initialize request is sent from the client to the server.
|
||||
It is sent once as the request after starting up the server.
|
||||
The requests parameter is of type {@link InitializeParams}
|
||||
the response if of type {@link InitializeResult} of a Thenable that
|
||||
resolves to such."""
|
||||
return self.send_request("initialize", params)
|
||||
|
||||
def shutdown(self) -> None:
|
||||
"""A shutdown request is sent from the client to the server.
|
||||
It is sent once when the client decides to shutdown the
|
||||
server. The only notification that is sent after a shutdown request
|
||||
is the exit event."""
|
||||
return self.send_request("shutdown")
|
||||
|
||||
def will_save_wait_until(
|
||||
self, params: lsp_types.WillSaveTextDocumentParams
|
||||
) -> Union[List["lsp_types.TextEdit"], None]:
|
||||
"""A document will save request is sent from the client to the server before
|
||||
the document is actually saved. The request can return an array of TextEdits
|
||||
which will be applied to the text document before it is saved. Please note that
|
||||
clients might drop results if computing the text edits took too long or if a
|
||||
server constantly fails on this request. This is done to keep the save fast and
|
||||
reliable."""
|
||||
return self.send_request("textDocument/willSaveWaitUntil", params)
|
||||
|
||||
def completion(
|
||||
self, params: lsp_types.CompletionParams
|
||||
) -> Union[List["lsp_types.CompletionItem"], "lsp_types.CompletionList", None]:
|
||||
"""Request to request completion at a given text document position. The request's
|
||||
parameter is of type {@link TextDocumentPosition} the response
|
||||
is of type {@link CompletionItem CompletionItem[]} or {@link CompletionList}
|
||||
or a Thenable that resolves to such.
|
||||
|
||||
The request can delay the computation of the {@link CompletionItem.detail `detail`}
|
||||
and {@link CompletionItem.documentation `documentation`} properties to the `completionItem/resolve`
|
||||
request. However, properties that are needed for the initial sorting and filtering, like `sortText`,
|
||||
`filterText`, `insertText`, and `textEdit`, must not be changed during resolve.
|
||||
"""
|
||||
return self.send_request("textDocument/completion", params)
|
||||
|
||||
def resolve_completion_item(
|
||||
self, params: lsp_types.CompletionItem
|
||||
) -> "lsp_types.CompletionItem":
|
||||
"""Request to resolve additional information for a given completion item.The request's
|
||||
parameter is of type {@link CompletionItem} the response
|
||||
is of type {@link CompletionItem} or a Thenable that resolves to such."""
|
||||
return self.send_request("completionItem/resolve", params)
|
||||
|
||||
def hover(
|
||||
self, params: lsp_types.HoverParams
|
||||
) -> Union["lsp_types.Hover", None]:
|
||||
"""Request to request hover information at a given text document position. The request's
|
||||
parameter is of type {@link TextDocumentPosition} the response is of
|
||||
type {@link Hover} or a Thenable that resolves to such."""
|
||||
return self.send_request("textDocument/hover", params)
|
||||
|
||||
def signature_help(
|
||||
self, params: lsp_types.SignatureHelpParams
|
||||
) -> Union["lsp_types.SignatureHelp", None]:
|
||||
return self.send_request("textDocument/signatureHelp", params)
|
||||
|
||||
def definition(
|
||||
self, params: lsp_types.DefinitionParams
|
||||
) -> Union["lsp_types.Definition", List["lsp_types.LocationLink"], None]:
|
||||
"""A request to resolve the definition location of a symbol at a given text
|
||||
document position. The request's parameter is of type [TextDocumentPosition]
|
||||
(#TextDocumentPosition) the response is of either type {@link Definition}
|
||||
or a typed array of {@link DefinitionLink} or a Thenable that resolves
|
||||
to such."""
|
||||
return self.send_request("textDocument/definition", params)
|
||||
|
||||
def references(
|
||||
self, params: lsp_types.ReferenceParams
|
||||
) -> Union[List["lsp_types.Location"], None]:
|
||||
"""A request to resolve project-wide references for the symbol denoted
|
||||
by the given text document position. The request's parameter is of
|
||||
type {@link ReferenceParams} the response is of type
|
||||
{@link Location Location[]} or a Thenable that resolves to such."""
|
||||
return self.send_request("textDocument/references", params)
|
||||
|
||||
def document_highlight(
|
||||
self, params: lsp_types.DocumentHighlightParams
|
||||
) -> Union[List["lsp_types.DocumentHighlight"], None]:
|
||||
"""Request to resolve a {@link DocumentHighlight} for a given
|
||||
text document position. The request's parameter is of type [TextDocumentPosition]
|
||||
(#TextDocumentPosition) the request response is of type [DocumentHighlight[]]
|
||||
(#DocumentHighlight) or a Thenable that resolves to such."""
|
||||
return self.send_request("textDocument/documentHighlight", params)
|
||||
|
||||
def document_symbol(
|
||||
self, params: lsp_types.DocumentSymbolParams
|
||||
) -> Union[
|
||||
List["lsp_types.SymbolInformation"], List["lsp_types.DocumentSymbol"], None
|
||||
]:
|
||||
"""A request to list all symbols found in a given text document. The request's
|
||||
parameter is of type {@link TextDocumentIdentifier} the
|
||||
response is of type {@link SymbolInformation SymbolInformation[]} or a Thenable
|
||||
that resolves to such."""
|
||||
return self.send_request("textDocument/documentSymbol", params)
|
||||
|
||||
def code_action(
|
||||
self, params: lsp_types.CodeActionParams
|
||||
) -> Union[List[Union["lsp_types.Command", "lsp_types.CodeAction"]], None]:
|
||||
"""A request to provide commands for the given text document and range."""
|
||||
return self.send_request("textDocument/codeAction", params)
|
||||
|
||||
def resolve_code_action(
|
||||
self, params: lsp_types.CodeAction
|
||||
) -> "lsp_types.CodeAction":
|
||||
"""Request to resolve additional information for a given code action.The request's
|
||||
parameter is of type {@link CodeAction} the response
|
||||
is of type {@link CodeAction} or a Thenable that resolves to such."""
|
||||
return self.send_request("codeAction/resolve", params)
|
||||
|
||||
def workspace_symbol(
|
||||
self, params: lsp_types.WorkspaceSymbolParams
|
||||
) -> Union[
|
||||
List["lsp_types.SymbolInformation"], List["lsp_types.WorkspaceSymbol"], None
|
||||
]:
|
||||
"""A request to list project-wide symbols matching the query string given
|
||||
by the {@link WorkspaceSymbolParams}. The response is
|
||||
of type {@link SymbolInformation SymbolInformation[]} or a Thenable that
|
||||
resolves to such.
|
||||
|
||||
@since 3.17.0 - support for WorkspaceSymbol in the returned data. Clients
|
||||
need to advertise support for WorkspaceSymbols via the client capability
|
||||
`workspace.symbol.resolveSupport`.
|
||||
"""
|
||||
return self.send_request("workspace/symbol", params)
|
||||
|
||||
def resolve_workspace_symbol(
|
||||
self, params: lsp_types.WorkspaceSymbol
|
||||
) -> "lsp_types.WorkspaceSymbol":
|
||||
"""A request to resolve the range inside the workspace
|
||||
symbol's location.
|
||||
|
||||
@since 3.17.0"""
|
||||
return self.send_request("workspaceSymbol/resolve", params)
|
||||
|
||||
def code_lens(
|
||||
self, params: lsp_types.CodeLensParams
|
||||
) -> Union[List["lsp_types.CodeLens"], None]:
|
||||
"""A request to provide code lens for the given text document."""
|
||||
return self.send_request("textDocument/codeLens", params)
|
||||
|
||||
def resolve_code_lens(
|
||||
self, params: lsp_types.CodeLens
|
||||
) -> "lsp_types.CodeLens":
|
||||
"""A request to resolve a command for a given code lens."""
|
||||
return self.send_request("codeLens/resolve", params)
|
||||
|
||||
def document_link(
|
||||
self, params: lsp_types.DocumentLinkParams
|
||||
) -> Union[List["lsp_types.DocumentLink"], None]:
|
||||
"""A request to provide document links"""
|
||||
return self.send_request("textDocument/documentLink", params)
|
||||
|
||||
def resolve_document_link(
|
||||
self, params: lsp_types.DocumentLink
|
||||
) -> "lsp_types.DocumentLink":
|
||||
"""Request to resolve additional information for a given document link. The request's
|
||||
parameter is of type {@link DocumentLink} the response
|
||||
is of type {@link DocumentLink} or a Thenable that resolves to such."""
|
||||
return self.send_request("documentLink/resolve", params)
|
||||
|
||||
def formatting(
|
||||
self, params: lsp_types.DocumentFormattingParams
|
||||
) -> Union[List["lsp_types.TextEdit"], None]:
|
||||
"""A request to to format a whole document."""
|
||||
return self.send_request("textDocument/formatting", params)
|
||||
|
||||
def range_formatting(
|
||||
self, params: lsp_types.DocumentRangeFormattingParams
|
||||
) -> Union[List["lsp_types.TextEdit"], None]:
|
||||
"""A request to to format a range in a document."""
|
||||
return self.send_request("textDocument/rangeFormatting", params)
|
||||
|
||||
def on_type_formatting(
|
||||
self, params: lsp_types.DocumentOnTypeFormattingParams
|
||||
) -> Union[List["lsp_types.TextEdit"], None]:
|
||||
"""A request to format a document on type."""
|
||||
return self.send_request("textDocument/onTypeFormatting", params)
|
||||
|
||||
def rename(
|
||||
self, params: lsp_types.RenameParams
|
||||
) -> Union["lsp_types.WorkspaceEdit", None]:
|
||||
"""A request to rename a symbol."""
|
||||
return self.send_request("textDocument/rename", params)
|
||||
|
||||
def prepare_rename(
|
||||
self, params: lsp_types.PrepareRenameParams
|
||||
) -> Union["lsp_types.PrepareRenameResult", None]:
|
||||
"""A request to test and perform the setup necessary for a rename.
|
||||
|
||||
@since 3.16 - support for default behavior"""
|
||||
return self.send_request("textDocument/prepareRename", params)
|
||||
|
||||
def execute_command(
|
||||
self, params: lsp_types.ExecuteCommandParams
|
||||
) -> Union["lsp_types.LSPAny", None]:
|
||||
"""A request send from the client to the server to execute a command. The request might return
|
||||
a workspace edit which the client will apply to the workspace."""
|
||||
return self.send_request("workspace/executeCommand", params)
|
||||
+8
-1
@@ -1,5 +1,6 @@
|
||||
import logging
|
||||
from pathlib import Path
|
||||
from typing import cast
|
||||
|
||||
import pytest
|
||||
from sensai.util.logging import configure
|
||||
@@ -7,7 +8,9 @@ from sensai.util.logging import configure
|
||||
from multilspy.language_server import SyncLanguageServer
|
||||
from multilspy.multilspy_config import Language, MultilspyConfig
|
||||
from multilspy.multilspy_logger import MultilspyLogger
|
||||
from serena.constants import USE_SOLID_LSP
|
||||
from serena.util.file_system import GitignoreParser
|
||||
from solidlsp.ls import SolidLanguageServer
|
||||
|
||||
configure(level=logging.DEBUG)
|
||||
|
||||
@@ -43,7 +46,11 @@ def create_ls(
|
||||
config = MultilspyConfig(code_language=language, ignored_paths=ignored_paths, trace_lsp_communication=trace_lsp_communication)
|
||||
logger = MultilspyLogger(log_level=log_level)
|
||||
|
||||
return SyncLanguageServer.create(config, logger, repo_path)
|
||||
if USE_SOLID_LSP:
|
||||
ls = SolidLanguageServer.create(config, logger, repo_path)
|
||||
return cast(SyncLanguageServer, ls) # TODO: Fix type
|
||||
else:
|
||||
return SyncLanguageServer.create(config, logger, repo_path)
|
||||
|
||||
|
||||
def create_default_ls(language: Language) -> SyncLanguageServer:
|
||||
|
||||
@@ -4,10 +4,12 @@ import pytest
|
||||
from mcp.server.fastmcp.tools.base import Tool as MCPTool
|
||||
|
||||
from serena.agent import SerenaAgent, ToolRegistry
|
||||
from serena.mcp import make_tool
|
||||
from serena.mcp import SerenaMCPFactory
|
||||
from serena.process_isolated_agent import ProcessIsolatedSerenaAgent, ProcessIsolatedTool
|
||||
from test.serena.test_serena_agent import SerenaConfigForTests
|
||||
|
||||
make_tool = SerenaMCPFactory.make_mcp_tool
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def in_memory_config():
|
||||
|
||||
@@ -4,7 +4,9 @@ import pytest
|
||||
from mcp.server.fastmcp.tools.base import Tool as MCPTool
|
||||
|
||||
from serena.agent import Tool, ToolRegistry
|
||||
from serena.mcp import make_tool
|
||||
from serena.mcp import SerenaMCPFactory
|
||||
|
||||
make_tool = SerenaMCPFactory.make_mcp_tool
|
||||
|
||||
|
||||
class BaseMockTool(Tool):
|
||||
|
||||
@@ -101,6 +101,15 @@ jupyter = [
|
||||
{ name = "tokenize-rt" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blinker"
|
||||
version = "1.9.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/21/28/9b3f50ce0e048515135495f198351908d99540d69bfdc8c1d15b73dc55ce/blinker-1.9.0.tar.gz", hash = "sha256:b4ce2265a7abece45e7cc896e98dbebe6cead56bcf805a3d23136d145f5445bf", size = 22460 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/10/cb/f2ad4230dc2eb1a74edf38f1a38b9b52277f75bef262d8908e60d957e13c/blinker-1.9.0-py3-none-any.whl", hash = "sha256:ba0efaa9080b619ff2f3459d1d500c57bddea4a6b424b60a91141db6fd2f08bc", size = 8458 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cachetools"
|
||||
version = "5.5.2"
|
||||
@@ -210,17 +219,20 @@ wheels = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastapi"
|
||||
version = "0.115.12"
|
||||
name = "flask"
|
||||
version = "3.1.1"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "pydantic" },
|
||||
{ name = "starlette" },
|
||||
{ name = "typing-extensions" },
|
||||
{ name = "blinker" },
|
||||
{ name = "click" },
|
||||
{ name = "itsdangerous" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "markupsafe" },
|
||||
{ name = "werkzeug" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/f4/55/ae499352d82338331ca1e28c7f4a63bfd09479b16395dce38cf50a39e2c2/fastapi-0.115.12.tar.gz", hash = "sha256:1e2c2a2646905f9e83d32f04a3f86aff4a286669c6c950ca95b5fd68c2602681", size = 295236 }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/c0/de/e47735752347f4128bcf354e0da07ef311a78244eba9e3dc1d4a5ab21a98/flask-3.1.1.tar.gz", hash = "sha256:284c7b8f2f58cb737f0cf1c30fd7eaf0ccfcde196099d24ecede3fc2005aa59e", size = 753440 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/50/b3/b51f09c2ba432a576fe63758bddc81f78f0c6309d9e5c10d194313bf021e/fastapi-0.115.12-py3-none-any.whl", hash = "sha256:e94613d6c05e27be7ffebdd6ea5f388112e5e430c8f7d6494a9d1d88d43e814d", size = 95164 },
|
||||
{ url = "https://files.pythonhosted.org/packages/3d/68/9d4508e893976286d2ead7f8f571314af6c2037af34853a30fd769c02e9d/flask-3.1.1-py3-none-any.whl", hash = "sha256:07aae2bb5eaf77993ef57e357491839f5fd9f4dc281593a81a9e4d79a24f295c", size = 103305 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -394,6 +406,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/d9/33/1f075bf72b0b747cb3288d011319aaf64083cf2efef8354174e3ed4540e2/ipython_pygments_lexers-1.1.1-py3-none-any.whl", hash = "sha256:a9462224a505ade19a605f71f8fa63c2048833ce50abc86768a0d81d876dc81c", size = 8074 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "itsdangerous"
|
||||
version = "2.2.0"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9c/cb/8ac0172223afbccb63986cc25049b154ecfb5e85932587206f42317be31d/itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173", size = 54410 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/04/96/92447566d16df59b2a776c0fb82dbc4d9e07cd95062562af01e408583fc4/itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef", size = 16234 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jedi"
|
||||
version = "0.19.2"
|
||||
@@ -959,7 +980,7 @@ source = { editable = "." }
|
||||
dependencies = [
|
||||
{ name = "docstring-parser" },
|
||||
{ name = "dotenv" },
|
||||
{ name = "fastapi" },
|
||||
{ name = "flask" },
|
||||
{ name = "jinja2" },
|
||||
{ name = "joblib" },
|
||||
{ name = "mcp" },
|
||||
@@ -1007,7 +1028,7 @@ requires-dist = [
|
||||
{ name = "black", extras = ["jupyter"], marker = "extra == 'dev'", specifier = ">=23.7.0" },
|
||||
{ name = "docstring-parser", specifier = ">=0.16" },
|
||||
{ name = "dotenv", specifier = ">=0.9.9" },
|
||||
{ name = "fastapi", specifier = ">=0.115.12" },
|
||||
{ name = "flask", specifier = ">=3.0.0" },
|
||||
{ name = "google-genai", marker = "extra == 'google'", specifier = ">=1.8.0" },
|
||||
{ name = "jinja2", specifier = ">=3.1.6" },
|
||||
{ name = "jinja2", marker = "extra == 'dev'" },
|
||||
@@ -1299,3 +1320,15 @@ wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/98/93/e36c73f78400a65f5e236cd376713c34182e6663f6889cd45a4a04d8f203/websockets-15.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:27ccee0071a0e75d22cb35849b1db43f2ecd3e161041ac1ee9d2352ddf72f065", size = 176828 },
|
||||
{ url = "https://files.pythonhosted.org/packages/fa/a8/5b41e0da817d64113292ab1f8247140aac61cbf6cfd085d6a0fa77f4984f/websockets-15.0.1-py3-none-any.whl", hash = "sha256:f7a866fbc1e97b5c617ee4116daaa09b722101d4a3c170c787450ba409f9736f", size = 169743 },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "werkzeug"
|
||||
version = "3.1.3"
|
||||
source = { registry = "https://pypi.org/simple" }
|
||||
dependencies = [
|
||||
{ name = "markupsafe" },
|
||||
]
|
||||
sdist = { url = "https://files.pythonhosted.org/packages/9f/69/83029f1f6300c5fb2471d621ab06f6ec6b3324685a2ce0f9777fd4a8b71e/werkzeug-3.1.3.tar.gz", hash = "sha256:60723ce945c19328679790e3282cc758aa4a6040e4bb330f53d30fa546d44746", size = 806925 }
|
||||
wheels = [
|
||||
{ url = "https://files.pythonhosted.org/packages/52/24/ab44c871b0f07f491e5d2ad12c9bd7358e527510618cb1b803a88e986db1/werkzeug-3.1.3-py3-none-any.whl", hash = "sha256:54b78bf3716d19a65be4fceccc0d1d7b89e608834989dfae50ea87564639213e", size = 224498 },
|
||||
]
|
||||
Reference in new issue
Block a user