diff options
author | Nate Sesti <sestinj@gmail.com> | 2023-09-10 00:16:55 -0700 |
---|---|---|
committer | Nate Sesti <sestinj@gmail.com> | 2023-09-10 00:16:55 -0700 |
commit | a6d21f979fce6135fd76923478f76000b1b343cf (patch) | |
tree | 4e8dba10eb00d6b5f4479cd27c5ba6d1457cae0a /continuedev | |
parent | f1a7d40d5200a2cf2b58969bf0a3a528680af938 (diff) | |
download | sncontinue-a6d21f979fce6135fd76923478f76000b1b343cf.tar.gz sncontinue-a6d21f979fce6135fd76923478f76000b1b343cf.tar.bz2 sncontinue-a6d21f979fce6135fd76923478f76000b1b343cf.zip |
feat: :sparkles: LSP connection over websockets
Diffstat (limited to 'continuedev')
7 files changed, 274 insertions, 890 deletions
diff --git a/continuedev/src/continuedev/core/lsp.py b/continuedev/src/continuedev/core/lsp.py index 86923fb7..181eea2e 100644 --- a/continuedev/src/continuedev/core/lsp.py +++ b/continuedev/src/continuedev/core/lsp.py @@ -1,282 +1,272 @@ import asyncio -import os -import socket -import subprocess import threading from typing import List, Optional +import aiohttp from pydantic import BaseModel -from pylsp.python_lsp import PythonLSPServer, start_tcp_lang_server +from pylsp.python_lsp import PythonLSPServer, start_ws_lang_server -from ..libs.lspclient.json_rpc_endpoint import JsonRpcEndpoint -from ..libs.lspclient.lsp_client import LspClient -from ..libs.lspclient.lsp_endpoint import LspEndpoint -from ..libs.lspclient.lsp_structs import Position as LspPosition -from ..libs.lspclient.lsp_structs import SymbolInformation, TextDocumentIdentifier from ..libs.util.logging import logger from ..models.filesystem import RangeInFile from ..models.main import Position, Range -class ReadPipe(threading.Thread): - def __init__(self, pipe): - threading.Thread.__init__(self) - self.pipe = pipe - - def run(self): - line = self.pipe.readline().decode("utf-8") - while line: - print(line) - line = self.pipe.readline().decode("utf-8") +def filepath_to_uri(filename: str) -> str: + return f"file://{filename}" -class SocketFileWrapper: - def __init__(self, sockfile): - self.sockfile = sockfile +def uri_to_filepath(uri: str) -> str: + if uri.startswith("file://"): + return uri.lstrip("file://") + else: + return uri - def write(self, data): - if isinstance(data, bytes): - data = data.decode("utf-8").replace("\r\n", "\n") - return self.sockfile.write(data) - def read(self, size=-1): - data = self.sockfile.read(size) - if isinstance(data, str): - data = data.replace("\n", "\r\n").encode("utf-8") - return data +PORT = 8099 - def readline(self, size=-1): - data = self.sockfile.readline(size) - if isinstance(data, str): - data = data.replace("\n", "\r\n").encode("utf-8") - return data - def flush(self): - return self.sockfile.flush() +class LSPClient: + def __init__(self, host: str, port: int, workspace_paths: List[str]): + self.host = host + self.port = port + self.session = aiohttp.ClientSession() + self.next_id = 0 + self.workspace_paths = workspace_paths - def close(self): - return self.sockfile.close() + async def connect(self): + print("Connecting") + self.ws = await self.session.ws_connect(f"ws://{self.host}:{self.port}/") + print("Connected") + async def send(self, data): + await self.ws.send_json(data) -async def create_json_rpc_endpoint(use_subprocess: Optional[str] = None): - if use_subprocess is None: - try: - threading.Thread( - target=start_tcp_lang_server, - args=("localhost", 8080, False, PythonLSPServer), - ).start() - except Exception as e: - logger.warning("Could not start TCP server: %s", e) + async def recv(self): + return await self.ws.receive_json() - await asyncio.sleep(2) + async def close(self): + await self.ws.close() + await self.session.close() - # Connect to the server - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - s.connect(("localhost", 8080)) + async def call_method(self, method_name, **kwargs): + body = { + "jsonrpc": "2.0", + "id": self.next_id, + "method": method_name, + "params": kwargs, + } + self.next_id += 1 + await self.send(body) + response = await self.recv() + return response - # Create a file-like object from the socket - sockfile = s.makefile("rw") - wrapped_sockfile = SocketFileWrapper(sockfile) - return JsonRpcEndpoint(wrapped_sockfile, wrapped_sockfile), None + async def initialize(self): + initialization_args = { + "capabilities": { + "textDocument": { + "codeAction": {"dynamicRegistration": True}, + "codeLens": {"dynamicRegistration": True}, + "colorProvider": {"dynamicRegistration": True}, + "completion": { + "completionItem": { + "commitCharactersSupport": True, + "documentationFormat": ["markdown", "plaintext"], + "snippetSupport": 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, + ] + }, + "contextSupport": True, + "dynamicRegistration": True, + }, + "definition": {"dynamicRegistration": True}, + "documentHighlight": {"dynamicRegistration": True}, + "documentLink": {"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, + ] + }, + }, + "formatting": {"dynamicRegistration": True}, + "hover": { + "contentFormat": ["markdown", "plaintext"], + "dynamicRegistration": True, + }, + "implementation": {"dynamicRegistration": True}, + "onTypeFormatting": {"dynamicRegistration": True}, + "publishDiagnostics": {"relatedInformation": True}, + "rangeFormatting": {"dynamicRegistration": True}, + "references": {"dynamicRegistration": True}, + "rename": {"dynamicRegistration": True}, + "signatureHelp": { + "dynamicRegistration": True, + "signatureInformation": { + "documentationFormat": ["markdown", "plaintext"] + }, + }, + "synchronization": { + "didSave": True, + "dynamicRegistration": True, + "willSave": True, + "willSaveWaitUntil": True, + }, + "typeDefinition": {"dynamicRegistration": True}, + }, + "workspace": { + "applyEdit": True, + "configuration": True, + "didChangeConfiguration": {"dynamicRegistration": True}, + "didChangeWatchedFiles": {"dynamicRegistration": True}, + "executeCommand": {"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, + ] + }, + }, + "workspaceEdit": {"documentChanges": True}, + "workspaceFolders": True, + }, + }, + "processId": 1234, + "rootPath": None, + "rootUri": filepath_to_uri(self.workspace_paths[0]), + "initializationOptions": {}, + "trace": "off", + "workspaceFolders": [ + { + "uri": filepath_to_uri(workspacePath), + "name": workspacePath.split("/")[-1], + } + for workspacePath in self.workspace_paths + ], + } + return await self.call_method("initialize", **initialization_args) + + async def goto_definition(self, filepath: str, position: Position): + return await self.call_method( + "textDocument/definition", + textDocument={"uri": filepath_to_uri(filepath)}, + position=position.dict(), + ) - else: - pyls_cmd = use_subprocess.split() - p = subprocess.Popen( - pyls_cmd, - stdin=subprocess.PIPE, - stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + async def document_symbol(self, filepath: str): + return await self.call_method( + "textDocument/documentSymbol", + textDocument={"uri": filepath_to_uri(filepath)}, ) - read_pipe = ReadPipe(p.stderr) - read_pipe.start() - return JsonRpcEndpoint(p.stdin, p.stdout), p -def filename_to_uri(filename: str) -> str: - return f"file://{filename}" +async def start_language_server() -> threading.Thread: + try: + thread = threading.Thread( + target=start_ws_lang_server, + args=(PORT, False, PythonLSPServer), + ) + thread.daemon = True + thread.start() + except Exception as e: + logger.warning("Could not start TCP server: %s", e) -def uri_to_filename(uri: str) -> str: - if uri.startswith("file://"): - return uri.lstrip("file://") - else: - return uri + await asyncio.sleep(2) + return thread -async def create_lsp_client(workspace_dir: str, use_subprocess: Optional[str] = None): - json_rpc_endpoint, process = await create_json_rpc_endpoint( - use_subprocess=use_subprocess - ) - lsp_endpoint = LspEndpoint(json_rpc_endpoint) - lsp_client = LspClient(lsp_endpoint) - capabilities = { - "textDocument": { - "codeAction": {"dynamicRegistration": True}, - "codeLens": {"dynamicRegistration": True}, - "colorProvider": {"dynamicRegistration": True}, - "completion": { - "completionItem": { - "commitCharactersSupport": True, - "documentationFormat": ["markdown", "plaintext"], - "snippetSupport": 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, - ] - }, - "contextSupport": True, - "dynamicRegistration": True, - }, - "definition": {"dynamicRegistration": True}, - "documentHighlight": {"dynamicRegistration": True}, - "documentLink": {"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, - ] - }, - }, - "formatting": {"dynamicRegistration": True}, - "hover": { - "contentFormat": ["markdown", "plaintext"], - "dynamicRegistration": True, - }, - "implementation": {"dynamicRegistration": True}, - "onTypeFormatting": {"dynamicRegistration": True}, - "publishDiagnostics": {"relatedInformation": True}, - "rangeFormatting": {"dynamicRegistration": True}, - "references": {"dynamicRegistration": True}, - "rename": {"dynamicRegistration": True}, - "signatureHelp": { - "dynamicRegistration": True, - "signatureInformation": { - "documentationFormat": ["markdown", "plaintext"] - }, - }, - "synchronization": { - "didSave": True, - "dynamicRegistration": True, - "willSave": True, - "willSaveWaitUntil": True, - }, - "typeDefinition": {"dynamicRegistration": True}, - }, - "workspace": { - "applyEdit": True, - "configuration": True, - "didChangeConfiguration": {"dynamicRegistration": True}, - "didChangeWatchedFiles": {"dynamicRegistration": True}, - "executeCommand": {"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, - ] - }, - }, - "workspaceEdit": {"documentChanges": True}, - "workspaceFolders": True, - }, - } - root_uri = filename_to_uri(workspace_dir) - dir_name = os.path.basename(workspace_dir) - workspace_folders = [{"name": dir_name, "uri": root_uri}] - lsp_client.initialize( - None, - None, - root_uri, - None, - capabilities, - "off", - workspace_folders, - ) - lsp_client.initialized() - return lsp_client, process + +class DocumentSymbol(BaseModel): + name: str + containerName: Optional[str] = None + kind: int + location: RangeInFile class ContinueLSPClient(BaseModel): workspace_dir: str - lsp_client: LspClient = None - use_subprocess: Optional[str] = None - lsp_process: Optional[subprocess.Popen] = None + + lsp_client: LSPClient = None + lsp_thread: Optional[threading.Thread] = None class Config: arbitrary_types_allowed = True @@ -287,26 +277,26 @@ class ContinueLSPClient(BaseModel): return original_dict async def start(self): - self.lsp_client, self.lsp_process = await create_lsp_client( - self.workspace_dir, use_subprocess=self.use_subprocess - ) + self.lsp_thread = await start_language_server() + self.lsp_client = LSPClient("localhost", PORT, [self.workspace_dir]) + await self.lsp_client.connect() + await self.lsp_client.initialize() async def stop(self): - self.lsp_client.shutdown() - self.lsp_client.exit() - if self.lsp_process is not None: - self.lsp_process.terminate() - self.lsp_process.wait() - self.lsp_process = None - - def goto_definition(self, position: Position, filename: str) -> List[RangeInFile]: - response = self.lsp_client.definition( - TextDocumentIdentifier(filename_to_uri(filename)), - LspPosition(position.line, position.character), + await self.lsp_client.close() + if self.lsp_thread: + self.lsp_thread.join() + + async def goto_definition( + self, position: Position, filename: str + ) -> List[RangeInFile]: + response = self.lsp_client.goto_definition( + filename, + position, ) return [ RangeInFile( - filepath=uri_to_filename(x.uri), + filepath=uri_to_filepath(x.uri), range=Range.from_shorthand( x.range.start.line, x.range.start.character, @@ -317,9 +307,22 @@ class ContinueLSPClient(BaseModel): for x in response ] - def get_symbols(self, filepath: str) -> List[SymbolInformation]: - response = self.lsp_client.documentSymbol( - TextDocumentIdentifier(filename_to_uri(filepath)) - ) - - return response + async def document_symbol(self, filepath: str) -> List: + response = await self.lsp_client.document_symbol(filepath) + return [ + DocumentSymbol( + name=x["name"], + containerName=x["containerName"], + kind=x["kind"], + location=RangeInFile( + filepath=uri_to_filepath(x["location"]["uri"]), + range=Range.from_shorthand( + x["location"]["range"]["start"]["line"], + x["location"]["range"]["start"]["character"], + x["location"]["range"]["end"]["line"], + x["location"]["range"]["end"]["character"], + ), + ), + ) + for x in response["result"] + ] diff --git a/continuedev/src/continuedev/core/sdk.py b/continuedev/src/continuedev/core/sdk.py index 658848c8..de209114 100644 --- a/continuedev/src/continuedev/core/sdk.py +++ b/continuedev/src/continuedev/core/sdk.py @@ -2,6 +2,8 @@ import os import traceback from typing import Coroutine, List, Optional, Union +from ..libs.util.create_async_task import create_async_task + from ..libs.llm import LLM from ..libs.util.devdata import dev_data_logger from ..libs.util.logging import logger @@ -103,9 +105,9 @@ class ContinueSDK(AbstractContinueSDK): logger.warning(f"Failed to start LSP client: {e}", exc_info=True) sdk.lsp = None - # create_async_task( - # start_lsp(), on_error=lambda e: logger.error("Failed to setup LSP: %s", e) - # ) + create_async_task( + start_lsp(), on_error=lambda e: logger.error("Failed to setup LSP: %s", e) + ) # When the config is loaded, setup posthog logger posthog_logger.setup(sdk.ide.unique_id, sdk.config.allow_anonymous_telemetry) diff --git a/continuedev/src/continuedev/libs/lspclient/json_rpc_endpoint.py b/continuedev/src/continuedev/libs/lspclient/json_rpc_endpoint.py deleted file mode 100644 index 80c51000..00000000 --- a/continuedev/src/continuedev/libs/lspclient/json_rpc_endpoint.py +++ /dev/null @@ -1,82 +0,0 @@ -from __future__ import print_function - -import json -import re -import threading - -JSON_RPC_REQ_FORMAT = "Content-Length: {json_string_len}\r\n\r\n{json_string}" -JSON_RPC_RES_REGEX = "Content-Length: ([0-9]*)\r\n" -# TODO: add content-type - - -class MyEncoder(json.JSONEncoder): - """ - Encodes an object in JSON - """ - - def default(self, o): - return o.__dict__ - - -class JsonRpcEndpoint(object): - """ - Thread safe JSON RPC endpoint implementation. Responsible to recieve and send JSON RPC messages, as described in the - protocol. More information can be found: https://www.jsonrpc.org/ - """ - - def __init__(self, stdin, stdout): - self.stdin = stdin - self.stdout = stdout - self.read_lock = threading.Lock() - self.write_lock = threading.Lock() - - @staticmethod - def __add_header(json_string): - """ - Adds a header for the given json string - - :param str json_string: The string - :return: the string with the header - """ - return JSON_RPC_REQ_FORMAT.format( - json_string_len=len(json_string), json_string=json_string - ) - - def send_request(self, message): - """ - Sends the given message. - - :param dict message: The message to send. - """ - json_string = json.dumps(message, cls=MyEncoder) - # print("sending:", json_string) - jsonrpc_req = self.__add_header(json_string) - with self.write_lock: - self.stdin.write(jsonrpc_req.encode()) - self.stdin.flush() - - def recv_response(self): - """ - Recives a message. - - :return: a message - """ - with self.read_lock: - line = self.stdout.readline() - if not line: - return None - # print(line) - line = line.decode() - # TODO: handle content type as well. - match = re.match(JSON_RPC_RES_REGEX, line) - if match is None or not match.groups(): - raise RuntimeError("Bad header: " + line) - size = int(match.groups()[0]) - line = self.stdout.readline() - if not line: - return None - line = line.decode() - # if line != "\r\n": - # raise RuntimeError("Bad header: missing newline") - jsonrpc_res = self.stdout.read(size + 2) - return json.loads(jsonrpc_res) diff --git a/continuedev/src/continuedev/libs/lspclient/lsp_client.py b/continuedev/src/continuedev/libs/lspclient/lsp_client.py deleted file mode 100644 index fe2db6ad..00000000 --- a/continuedev/src/continuedev/libs/lspclient/lsp_client.py +++ /dev/null @@ -1,150 +0,0 @@ -from .lsp_structs import Location, SignatureHelp, SymbolInformation - - -class LspClient(object): - def __init__(self, lsp_endpoint): - """ - Constructs a new LspClient instance. - - :param lsp_endpoint: TODO - """ - self.lsp_endpoint = lsp_endpoint - - def initialize( - self, - processId, - rootPath, - rootUri, - initializationOptions, - capabilities, - trace, - workspaceFolders, - ): - """ - The initialize request is sent as the first request from the client to the server. If the server receives a request or notification - before the initialize request it should act as follows: - - 1. For a request the response should be an error with code: -32002. The message can be picked by the server. - 2. Notifications should be dropped, except for the exit notification. This will allow the exit of a server without an initialize request. - - Until the server has responded to the initialize request with an InitializeResult, the client must not send any additional requests or - notifications to the server. In addition the server is not allowed to send any requests or notifications to the client until it has responded - with an InitializeResult, with the exception that during the initialize request the server is allowed to send the notifications window/showMessage, - window/logMessage and telemetry/event as well as the window/showMessageRequest request to the client. - - The initialize request may only be sent once. - - :param int processId: The process Id of the parent process that started the server. Is null if the process has not been started by another process. - If the parent process is not alive then the server should exit (see exit notification) its process. - :param str rootPath: The rootPath of the workspace. Is null if no folder is open. Deprecated in favour of rootUri. - :param DocumentUri rootUri: The rootUri of the workspace. Is null if no folder is open. If both `rootPath` and `rootUri` are set - `rootUri` wins. - :param any initializationOptions: User provided initialization options. - :param ClientCapabilities capabilities: The capabilities provided by the client (editor or tool). - :param Trace trace: The initial trace setting. If omitted trace is disabled ('off'). - :param list workspaceFolders: The workspace folders configured in the client when the server starts. This property is only available if the client supports workspace folders. - It can be `null` if the client supports workspace folders but none are configured. - """ - self.lsp_endpoint.start() - return self.lsp_endpoint.call_method( - "initialize", - processId=processId, - rootPath=rootPath, - rootUri=rootUri, - initializationOptions=initializationOptions, - capabilities=capabilities, - trace=trace, - workspaceFolders=workspaceFolders, - ) - - def initialized(self): - """ - The initialized notification is sent from the client to the server after the client received the result of the initialize request - but before the client is sending any other request or notification to the server. The server can use the initialized notification - for example to dynamically register capabilities. The initialized notification may only be sent once. - """ - self.lsp_endpoint.send_notification("initialized") - - def shutdown(self): - """ - The initialized notification is sent from the client to the server after the client received the result of the initialize request - but before the client is sending any other request or notification to the server. The server can use the initialized notification - for example to dynamically register capabilities. The initialized notification may only be sent once. - """ - self.lsp_endpoint.stop() - return self.lsp_endpoint.call_method("shutdown") - - def exit(self): - """ - The initialized notification is sent from the client to the server after the client received the result of the initialize request - but before the client is sending any other request or notification to the server. The server can use the initialized notification - for example to dynamically register capabilities. The initialized notification may only be sent once. - """ - self.lsp_endpoint.send_notification("exit") - - def didOpen(self, textDocument): - """ - The document open notification is sent from the client to the server to signal newly opened text documents. The document's truth is - now managed by the client and the server must not try to read the document's truth using the document's uri. Open in this sense - means it is managed by the client. It doesn't necessarily mean that its content is presented in an editor. An open notification must - not be sent more than once without a corresponding close notification send before. This means open and close notification must be - balanced and the max open count for a particular textDocument is one. Note that a server's ability to fulfill requests is independent - of whether a text document is open or closed. - - The DidOpenTextDocumentParams contain the language id the document is associated with. If the language Id of a document changes, the - client needs to send a textDocument/didClose to the server followed by a textDocument/didOpen with the new language id if the server - handles the new language id as well. - - :param TextDocumentItem textDocument: The initial trace setting. If omitted trace is disabled ('off'). - """ - return self.lsp_endpoint.send_notification( - "textDocument/didOpen", textDocument=textDocument - ) - - def documentSymbol(self, textDocument): - """ - The document symbol request is sent from the client to the server to return a flat list of all symbols found in a given text document. - Neither the symbol's location range nor the symbol's container name should be used to infer a hierarchy. - - :param TextDocumentItem textDocument: The text document. - """ - result_dict = self.lsp_endpoint.call_method( - "textDocument/documentSymbol", textDocument=textDocument - ) - return [SymbolInformation(**sym) for sym in result_dict] - - def definition(self, textDocument, position): - """ - The goto definition request is sent from the client to the server to resolve the definition location of a symbol at a given text document position. - - :param TextDocumentItem textDocument: The text document. - :param Position position: The position inside the text document.. - """ - result_dict = self.lsp_endpoint.call_method( - "textDocument/definition", textDocument=textDocument, position=position - ) - return [Location(**l) for l in result_dict] - - def typeDefinition(self, textDocument, position): - """ - The goto type definition request is sent from the client to the server to resolve the type definition location of a symbol at a given text document position. - - :param TextDocumentItem textDocument: The text document. - :param Position position: The position inside the text document.. - """ - result_dict = self.lsp_endpoint.call_method( - "textDocument/definition", textDocument=textDocument, position=position - ) - return [Location(**l) for l in result_dict] - - def signatureHelp(self, textDocument, position): - """ - The signature help request is sent from the client to the server to request signature information at a given cursor position. - - :param TextDocumentItem textDocument: The text document. - :param Position position: The position inside the text document.. - """ - result_dict = self.lsp_endpoint.call_method( - "textDocument/signatureHelp", textDocument=textDocument, position=position - ) - return SignatureHelp(**result_dict) diff --git a/continuedev/src/continuedev/libs/lspclient/lsp_endpoint.py b/continuedev/src/continuedev/libs/lspclient/lsp_endpoint.py deleted file mode 100644 index 9b37a06d..00000000 --- a/continuedev/src/continuedev/libs/lspclient/lsp_endpoint.py +++ /dev/null @@ -1,73 +0,0 @@ -from __future__ import print_function - -import threading -import time - - -class LspEndpoint(threading.Thread): - def __init__(self, json_rpc_endpoint, default_callback=print, callbacks={}): - threading.Thread.__init__(self) - self.json_rpc_endpoint = json_rpc_endpoint - self.callbacks = callbacks - self.default_callback = default_callback - self.event_dict = {} - self.response_dict = {} - self.next_id = 0 - # self.daemon = True - self.shutdown_flag = False - - def handle_result(self, jsonrpc_res): - self.response_dict[jsonrpc_res["id"]] = jsonrpc_res - cond = self.event_dict[jsonrpc_res["id"]] - cond.acquire() - cond.notify() - cond.release() - - def stop(self): - self.shutdown_flag = True - - def run(self): - while not self.shutdown_flag: - time.sleep(0.1) - jsonrpc_message = self.json_rpc_endpoint.recv_response() - - if jsonrpc_message is None: - print("server quit") - break - - # print("recieved message:", jsonrpc_message) - if "result" in jsonrpc_message or "error" in jsonrpc_message: - self.handle_result(jsonrpc_message) - elif "method" in jsonrpc_message: - if jsonrpc_message["method"] in self.callbacks: - self.callbacks[jsonrpc_message["method"]](jsonrpc_message) - else: - self.default_callback(jsonrpc_message) - else: - print("unknown jsonrpc message") - # print(jsonrpc_message) - - def send_message(self, method_name, params, id=None): - message_dict = {} - message_dict["jsonrpc"] = "2.0" - if id is not None: - message_dict["id"] = id - message_dict["method"] = method_name - message_dict["params"] = params - self.json_rpc_endpoint.send_request(message_dict) - - def call_method(self, method_name, **kwargs): - current_id = self.next_id - self.next_id += 1 - cond = threading.Condition() - self.event_dict[current_id] = cond - cond.acquire() - self.send_message(method_name, kwargs, current_id) - cond.wait() - cond.release() - # TODO: check if error, and throw an exception - response = self.response_dict[current_id] - return response["result"] - - def send_notification(self, method_name, **kwargs): - self.send_message(method_name, kwargs) diff --git a/continuedev/src/continuedev/libs/lspclient/lsp_structs.py b/continuedev/src/continuedev/libs/lspclient/lsp_structs.py deleted file mode 100644 index 2f0940d4..00000000 --- a/continuedev/src/continuedev/libs/lspclient/lsp_structs.py +++ /dev/null @@ -1,316 +0,0 @@ -def to_type(o, new_type): - ''' - Helper funciton that receives an object or a dict and convert it to a new given type. - - :param object|dict o: The object to convert - :param Type new_type: The type to convert to. - ''' - if new_type == type(o): - return o - else: - return new_type(**o) - - -class Position(object): - def __init__(self, line, character): - """ - Constructs a new Position instance. - - :param int line: Line position in a document (zero-based). - :param int character: Character offset on a line in a document (zero-based). - """ - self.line = line - self.character = character - - -class Range(object): - def __init__(self, start, end): - """ - Constructs a new Range instance. - - :param Position start: The range's start position. - :param Position end: The range's end position. - """ - self.start = to_type(start, Position) - self.end = to_type(end, Position) - - -class Location(object): - """ - Represents a location inside a resource, such as a line inside a text file. - """ - def __init__(self, uri, range): - """ - Constructs a new Range instance. - - :param str uri: Resource file. - :param Range range: The range inside the file - """ - self.uri = uri - self.range = to_type(range, Range) - - -class Diagnostic(object): - def __init__(self, range, severity, code, source, message, relatedInformation): - """ - Constructs a new Diagnostic instance. - :param Range range: The range at which the message applies.Resource file. - :param int severity: The diagnostic's severity. Can be omitted. If omitted it is up to the - client to interpret diagnostics as error, warning, info or hint. - :param str code: The diagnostic's code, which might appear in the user interface. - :param str source: A human-readable string describing the source of this - diagnostic, e.g. 'typescript' or 'super lint'. - :param str message: The diagnostic's message. - :param list relatedInformation: An array of related diagnostic information, e.g. when symbol-names within - a scope collide all definitions can be marked via this property. - """ - self.range = range - self.severity = severity - self.code = code - self.source = source - self.message = message - self.relatedInformation = relatedInformation - - -class DiagnosticSeverity(object): - Error = 1 - Warning = 2 # TODO: warning is known in python - Information = 3 - Hint = 4 - - -class DiagnosticRelatedInformation(object): - def __init__(self, location, message): - """ - Constructs a new Diagnostic instance. - :param Location location: The location of this related diagnostic information. - :param str message: The message of this related diagnostic information. - """ - self.location = location - self.message = message - - -class Command(object): - def __init__(self, title, command, arguments): - """ - Constructs a new Diagnostic instance. - :param str title: Title of the command, like `save`. - :param str command: The identifier of the actual command handler. - :param list argusments: Arguments that the command handler should be invoked with. - """ - self.title = title - self.command = command - self.arguments = arguments - - -class TextDocumentItem(object): - """ - An item to transfer a text document from the client to the server. - """ - def __init__(self, uri, languageId, version, text): - """ - Constructs a new Diagnostic instance. - - :param DocumentUri uri: Title of the command, like `save`. - :param str languageId: The identifier of the actual command handler. - :param int version: Arguments that the command handler should be invoked with. - :param str text: Arguments that the command handler should be invoked with. - """ - self.uri = uri - self.languageId = languageId - self.version = version - self.text = text - - -class TextDocumentIdentifier(object): - """ - Text documents are identified using a URI. On the protocol level, URIs are passed as strings. - """ - def __init__(self, uri): - """ - Constructs a new TextDocumentIdentifier instance. - - :param DocumentUri uri: The text document's URI. - """ - self.uri = uri - -class TextDocumentPositionParams(object): - """ - A parameter literal used in requests to pass a text document and a position inside that document. - """ - def __init__(self, textDocument, position): - """ - Constructs a new TextDocumentPositionParams instance. - - :param TextDocumentIdentifier textDocument: The text document. - :param Position position: The position inside the text document. - """ - self.textDocument = textDocument - self.position = position - - -class LANGUAGE_IDENTIFIER: - BAT="bat" - BIBTEX="bibtex" - CLOJURE="clojure" - COFFESCRIPT="coffeescript" - C="c" - CPP="cpp" - CSHARP="csharp" - CSS="css" - DIFF="diff" - DOCKERFILE="dockerfile" - FSHARP="fsharp" - GIT_COMMIT="git-commit" - GIT_REBASE="git-rebase" - GO="go" - GROOVY="groovy" - HANDLEBARS="handlebars" - HTML="html" - INI="ini" - JAVA="java" - JAVASCRIPT="javascript" - JSON="json" - LATEX="latex" - LESS="less" - LUA="lua" - MAKEFILE="makefile" - MARKDOWN="markdown" - OBJECTIVE_C="objective-c" - OBJECTIVE_CPP="objective-cpp" - Perl="perl" - PHP="php" - POWERSHELL="powershell" - PUG="jade" - PYTHON="python" - R="r" - RAZOR="razor" - RUBY="ruby" - RUST="rust" - SASS="sass" - SCSS="scss" - ShaderLab="shaderlab" - SHELL_SCRIPT="shellscript" - SQL="sql" - SWIFT="swift" - TYPE_SCRIPT="typescript" - TEX="tex" - VB="vb" - XML="xml" - XSL="xsl" - YAML="yaml" - - -class SymbolKind(object): - File = 1 - Module = 2 - Namespace = 3 - Package = 4 - Class = 5 - Method = 6 - Property = 7 - Field = 8 - Constructor = 9 - Enum = 10 - Interface = 11 - Function = 12 - Variable = 13 - Constant = 14 - String = 15 - Number = 16 - Boolean = 17 - Array = 18 - Object = 19 - Key = 20 - Null = 21 - EnumMember = 22 - Struct = 23 - Event = 24 - Operator = 25 - TypeParameter = 26 - - -class SymbolInformation(object): - """ - Represents information about programming constructs like variables, classes, interfaces etc. - """ - def __init__(self, name, kind, location, containerName, deprecated=False): - """ - Constructs a new SymbolInformation instance. - - :param str name: The name of this symbol. - :param int kind: The kind of this symbol. - :param bool Location: The location of this symbol. The location's range is used by a tool - to reveal the location in the editor. If the symbol is selected in the - tool the range's start information is used to position the cursor. So - the range usually spans more then the actual symbol's name and does - normally include things like visibility modifiers. - - The range doesn't have to denote a node range in the sense of a abstract - syntax tree. It can therefore not be used to re-construct a hierarchy of - the symbols. - :param str containerName: The name of the symbol containing this symbol. This information is for - user interface purposes (e.g. to render a qualifier in the user interface - if necessary). It can't be used to re-infer a hierarchy for the document - symbols. - :param bool deprecated: Indicates if this symbol is deprecated. - """ - self.name = name - self.kind = kind - self.deprecated = deprecated - self.location = to_type(location, Location) - self.containerName = containerName - - -class ParameterInformation(object): - """ - Represents a parameter of a callable-signature. A parameter can - have a label and a doc-comment. - """ - def __init__(self, label, documentation=""): - """ - Constructs a new ParameterInformation instance. - - :param str label: The label of this parameter. Will be shown in the UI. - :param str documentation: The human-readable doc-comment of this parameter. Will be shown in the UI but can be omitted. - """ - self.label = label - self.documentation = documentation - - -class SignatureInformation(object): - """ - Represents the signature of something callable. A signature - can have a label, like a function-name, a doc-comment, and - a set of parameters. - """ - def __init__(self, label, documentation="", parameters=[]): - """ - Constructs a new SignatureInformation instance. - - :param str label: The label of this signature. Will be shown in the UI. - :param str documentation: The human-readable doc-comment of this signature. Will be shown in the UI but can be omitted. - :param ParameterInformation[] parameters: The parameters of this signature. - """ - self.label = label - self.documentation = documentation - self.parameters = [to_type(parameter, ParameterInformation) for parameter in parameters] - - -class SignatureHelp(object): - """ - Signature help represents the signature of something - callable. There can be multiple signature but only one - active and only one active parameter. - """ - def __init__(self, signatures, activeSignature=0, activeParameter=0): - """ - Constructs a new SignatureHelp instance. - - :param SignatureInformation[] signatures: One or more signatures. - :param int activeSignature: - :param int activeParameter: - """ - self.signatures = [to_type(signature, SignatureInformation) for signature in signatures] - self.activeSignature = activeSignature - self.activeParameter = activeParameter
\ No newline at end of file diff --git a/continuedev/src/continuedev/plugins/steps/on_traceback.py b/continuedev/src/continuedev/plugins/steps/on_traceback.py index 078309d0..3a96a8c7 100644 --- a/continuedev/src/continuedev/plugins/steps/on_traceback.py +++ b/continuedev/src/continuedev/plugins/steps/on_traceback.py @@ -175,7 +175,7 @@ async def get_function_body(frame: TracebackFrame, sdk: ContinueSDK) -> Optional if sdk.lsp is None: return None - document_symbols = sdk.lsp.get_symbols(frame.filepath) + document_symbols = await sdk.lsp.document_symbol(frame.filepath) for symbol in document_symbols: if symbol.name == frame.function: r = symbol.location.range |