mirror of
https://github.com/apple/sourcekit-lsp.git
synced 2026-03-02 18:23:24 +01:00
214 lines
7.5 KiB
Python
214 lines
7.5 KiB
Python
#!/usr/bin/env python3
|
|
##===----------------------------------------------------------------------===##
|
|
##
|
|
## This source file is part of the Swift.org open source project
|
|
##
|
|
## Copyright (c) 2014 - 2025 Apple Inc. and the Swift project authors
|
|
## Licensed under Apache License v2.0 with Runtime Library Exception
|
|
##
|
|
## See https://swift.org/LICENSE.txt for license information
|
|
## See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
|
|
##
|
|
##===----------------------------------------------------------------------===##
|
|
|
|
import json
|
|
import sys
|
|
from typing import Dict, List, Optional
|
|
|
|
|
|
class RequestError(Exception):
|
|
"""
|
|
An error that can be thrown from a request handling function in `AbstractBuildServer` to return an error response to
|
|
SourceKit-LSP.
|
|
"""
|
|
|
|
code: int
|
|
message: str
|
|
|
|
def __init__(self, code: int, message: str):
|
|
self.code = code
|
|
self.message = message
|
|
|
|
|
|
class AbstractBuildServer:
|
|
"""
|
|
An abstract class to implement a BSP server in Python for SourceKit-LSP testing purposes.
|
|
"""
|
|
|
|
def run(self):
|
|
"""
|
|
Run the build server. This should be called from the top-level code of the build server's Python file.
|
|
"""
|
|
while True:
|
|
line = sys.stdin.readline()
|
|
if len(line) == 0:
|
|
break
|
|
|
|
assert line.startswith("Content-Length:")
|
|
length = int(line[len("Content-Length:") :])
|
|
sys.stdin.readline()
|
|
message = json.loads(sys.stdin.read(length))
|
|
|
|
try:
|
|
result = self.handle_message(message)
|
|
if result is not None:
|
|
response_message: Dict[str, object] = {
|
|
"jsonrpc": "2.0",
|
|
"id": message["id"],
|
|
"result": result,
|
|
}
|
|
self.send_raw_message(response_message)
|
|
except RequestError as e:
|
|
error_response_message: Dict[str, object] = {
|
|
"jsonrpc": "2.0",
|
|
"id": message["id"],
|
|
"error": {
|
|
"code": e.code,
|
|
"message": e.message,
|
|
},
|
|
}
|
|
self.send_raw_message(error_response_message)
|
|
|
|
def handle_message(self, message: Dict[str, object]) -> Optional[Dict[str, object]]:
|
|
"""
|
|
Dispatch handling of the given method, received from SourceKit-LSP to the message handling function.
|
|
"""
|
|
method: str = str(message["method"])
|
|
params: Dict[str, object] = message["params"] # type: ignore
|
|
if method == "build/exit":
|
|
return self.exit(params)
|
|
elif method == "build/initialize":
|
|
return self.initialize(params)
|
|
elif method == "build/initialized":
|
|
return self.initialized(params)
|
|
elif method == "build/shutdown":
|
|
return self.shutdown(params)
|
|
elif method == "buildTarget/prepare":
|
|
return self.buildtarget_prepare(params)
|
|
elif method == "buildTarget/sources":
|
|
return self.buildtarget_sources(params)
|
|
elif method == "textDocument/registerForChanges":
|
|
return self.register_for_changes(params)
|
|
elif method == "textDocument/sourceKitOptions":
|
|
return self.textdocument_sourcekitoptions(params)
|
|
elif method == "workspace/didChangeWatchedFiles":
|
|
return self.workspace_did_change_watched_files(params)
|
|
elif method == "workspace/buildTargets":
|
|
return self.workspace_build_targets(params)
|
|
elif method == "workspace/waitForBuildSystemUpdates":
|
|
return self.workspace_waitForBuildSystemUpdates(params)
|
|
|
|
# ignore other notifications
|
|
if "id" in message:
|
|
raise RequestError(code=-32601, message=f"Method not found: {method}")
|
|
|
|
def send_raw_message(self, message: Dict[str, object]):
|
|
"""
|
|
Send a raw message to SourceKit-LSP. The message needs to have all JSON-RPC wrapper fields.
|
|
|
|
Subclasses should not call this directly
|
|
"""
|
|
message_str = json.dumps(message)
|
|
sys.stdout.buffer.write(
|
|
f"Content-Length: {len(message_str)}\r\n\r\n{message_str}".encode("utf-8")
|
|
)
|
|
sys.stdout.flush()
|
|
|
|
def send_notification(self, method: str, params: Dict[str, object]):
|
|
"""
|
|
Send a notification with the given method and parameters to SourceKit-LSP.
|
|
"""
|
|
message: Dict[str, object] = {
|
|
"jsonrpc": "2.0",
|
|
"method": method,
|
|
"params": params,
|
|
}
|
|
self.send_raw_message(message)
|
|
|
|
# Message handling functions.
|
|
# Subclasses should override these to provide functionality.
|
|
|
|
def exit(self, notification: Dict[str, object]) -> None:
|
|
pass
|
|
|
|
def initialize(self, request: Dict[str, object]) -> Dict[str, object]:
|
|
return {
|
|
"displayName": "test server",
|
|
"version": "0.1",
|
|
"bspVersion": "2.0",
|
|
"rootUri": "blah",
|
|
"capabilities": {"languageIds": ["swift", "c", "cpp", "objective-c", "objective-c"]},
|
|
"data": {
|
|
"sourceKitOptionsProvider": True,
|
|
},
|
|
}
|
|
|
|
def initialized(self, notification: Dict[str, object]) -> None:
|
|
pass
|
|
|
|
def register_for_changes(self, notification: Dict[str, object]):
|
|
pass
|
|
|
|
def textdocument_sourcekitoptions(
|
|
self, request: Dict[str, object]
|
|
) -> Dict[str, object]:
|
|
raise RequestError(
|
|
code=-32601, message=f"'textDocument/sourceKitOptions' not implemented"
|
|
)
|
|
|
|
def shutdown(self, request: Dict[str, object]) -> Dict[str, object]:
|
|
return {}
|
|
|
|
def buildtarget_prepare(self, request: Dict[str, object]) -> Dict[str, object]:
|
|
raise RequestError(
|
|
code=-32601, message=f"'buildTarget/prepare' not implemented"
|
|
)
|
|
|
|
def buildtarget_sources(self, request: Dict[str, object]) -> Dict[str, object]:
|
|
raise RequestError(
|
|
code=-32601, message=f"'buildTarget/sources' not implemented"
|
|
)
|
|
|
|
def workspace_did_change_watched_files(self, notification: Dict[str, object]) -> None:
|
|
pass
|
|
|
|
def workspace_build_targets(self, request: Dict[str, object]) -> Dict[str, object]:
|
|
raise RequestError(
|
|
code=-32601, message=f"'workspace/buildTargets' not implemented"
|
|
)
|
|
|
|
def workspace_waitForBuildSystemUpdates(self, request: Dict[str, object]) -> Dict[str, object]:
|
|
return {}
|
|
|
|
|
|
class LegacyBuildServer(AbstractBuildServer):
|
|
def send_sourcekit_options_changed(self, uri: str, options: List[str]):
|
|
"""
|
|
Send a `build/sourceKitOptionsChanged` notification to SourceKit-LSP, informing it about new build settings
|
|
using the old push-based settings model.
|
|
"""
|
|
self.send_notification(
|
|
"build/sourceKitOptionsChanged",
|
|
{
|
|
"uri": uri,
|
|
"updatedOptions": {"options": options},
|
|
},
|
|
)
|
|
|
|
"""
|
|
A build server that doesn't declare the `sourceKitOptionsProvider` and uses the push-based settings model.
|
|
"""
|
|
|
|
def initialize(self, request: Dict[str, object]) -> Dict[str, object]:
|
|
return {
|
|
"displayName": "test server",
|
|
"version": "0.1",
|
|
"bspVersion": "2.0",
|
|
"rootUri": "blah",
|
|
"capabilities": {"languageIds": ["a", "b"]},
|
|
"data": {
|
|
"indexDatabasePath": "some/index/db/path",
|
|
"indexStorePath": "some/index/store/path",
|
|
},
|
|
}
|