Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 26 additions & 28 deletions blarify/code_references/lsp_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -56,28 +56,28 @@ def _get_language_definition_for_extension(self, extension: str) -> LanguageDefi
else:
raise FileExtensionNotSupported(f'File extension "{extension}" is not supported)')

def _create_lsp_server(self, language_definitions: LanguageDefinitions):
def _create_lsp_server(self, language_definitions: LanguageDefinitions, timeout=15) -> SyncLanguageServer:
language = language_definitions.get_language_name()

config = MultilspyConfig.from_dict({"code_language": language})

logger = MultilspyLogger()
lsp = SyncLanguageServer.create(config, logger, PathCalculator.uri_to_path(self.root_uri))
lsp = SyncLanguageServer.create(config, logger, PathCalculator.uri_to_path(self.root_uri), timeout=timeout)
return lsp

def start(self) -> None:
"""
DEPRECATED, LSP servers are started on demand
"""

def _get_or_create_lsp_server(self, extension):
def _get_or_create_lsp_server(self, extension, timeout=15) -> SyncLanguageServer:
language_definitions = self._get_language_definition_for_extension(extension)
language = language_definitions.get_language_name()

if language in self.language_to_lsp_server:
return self.language_to_lsp_server[language]
else:
new_lsp = self._create_lsp_server(language_definitions)
new_lsp = self._create_lsp_server(language_definitions, timeout)
self.language_to_lsp_server[language] = new_lsp
self._initialize_lsp_server(language, new_lsp)
return new_lsp
Expand All @@ -94,33 +94,31 @@ def initialize_directory(self, file) -> None:

def get_paths_where_node_is_referenced(self, node: DefinitionNode) -> list[Reference]:
server = self._get_or_create_lsp_server(node.extension)
references = self._request_references_with_fallback(node, server)

if not references:
return []
references = self._request_references_with_exponential_backoff(node, server)

return [Reference(reference) for reference in references]

def _request_references_with_fallback(self, node, lsp):
try:
references = lsp.request_references(
file_path=PathCalculator.get_relative_path_from_uri(root_uri=self.root_uri, uri=node.path),
line=node.definition_range.start_dict["line"],
column=node.definition_range.start_dict["character"],
)

except (TimeoutError, ConnectionResetError) as e:
logger.warning(f"Error requesting references: {e}, attempting to restart LSP server")

self._restart_lsp_for_extension(node)
lsp = self._get_or_create_lsp_server(node.extension)
references = lsp.request_references(
file_path=PathCalculator.get_relative_path_from_uri(root_uri=self.root_uri, uri=node.path),
line=node.definition_range.start_dict["line"],
column=node.definition_range.start_dict["character"],
)

return references
def _request_references_with_exponential_backoff(self, node, lsp):
timeout = 10
for _ in range(1, 3):
try:
references = lsp.request_references(
file_path=PathCalculator.get_relative_path_from_uri(root_uri=self.root_uri, uri=node.path),
line=node.definition_range.start_dict["line"],
column=node.definition_range.start_dict["character"],
)

return references

except (TimeoutError, ConnectionResetError) as e:
timeout = timeout * 2

logger.warning(f"Error requesting references, attempting to restart LSP server with timeout {timeout}")
self._restart_lsp_for_extension(node)
lsp = self._get_or_create_lsp_server(node.extension, timeout)

logger.error("Failed to get references, returning empty list")
return []

def _restart_lsp_for_extension(self, node):
language_definitions = self._get_language_definition_for_extension(node.extension)
Expand Down
4 changes: 3 additions & 1 deletion blarify/examples/graph_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@


def build(root_path: str = None):
graph_builder = GraphBuilder(root_path=root_path, extensions_to_skip=[".json"], names_to_skip=["__pycache__"])
graph_builder = GraphBuilder(
root_path=root_path, extensions_to_skip=[".json"], names_to_skip=["__pycache__", ".venv", ".git"]
)
graph = graph_builder.build()

relationships = graph.get_relationships_as_objects()
Expand Down
2 changes: 2 additions & 0 deletions blarify/project_graph_creator.py
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,8 @@ def create_relationship_from_references(self, file_nodes: List["Node"]) -> None:
if node.label == NodeLabels.FILE:
continue

logger.debug(f"Processing node {node.name}")

tree_sitter_helper = self._get_tree_sitter_for_file_extension(node.extension)
references_relationships.extend(
self.create_node_relationships(node=node, tree_sitter_helper=tree_sitter_helper)
Expand Down
32 changes: 23 additions & 9 deletions blarify/vendor/multilspy/language_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@
from .multilspy_exceptions import MultilspyException
from .multilspy_utils import PathUtils, FileUtils, TextUtils
from pathlib import PurePath
from typing import AsyncIterator, Iterator, List, Dict, Union, Tuple
from typing import AsyncIterator, Iterator, List, Dict, Optional, Union, Tuple
from .type_helpers import ensure_all_methods_implemented


Expand Down Expand Up @@ -111,6 +111,12 @@ def create(
from blarify.vendor.multilspy.language_servers.solargraph.solargraph import Solargraph

return Solargraph(config, logger, repository_root_path)
elif config.code_language == Language.DART:
from blarify.vendor.multilspy.language_servers.dart_language_server.dart_language_server import (
DartLanguageServer,
)

return DartLanguageServer(config, logger, repository_root_path)
else:
logger.log(
f"Language {config.code_language} is not supported", logging.ERROR
Expand Down Expand Up @@ -751,14 +757,21 @@ class SyncLanguageServer:
It is used to communicate with Language Servers of different programming languages.
"""

def __init__(self, language_server: LanguageServer) -> None:
def __init__(
self, language_server: LanguageServer, timeout: Optional[int] = None
) -> None:
self.language_server = language_server
self.loop = None
self.loop_thread = None
self.timeout = timeout

@classmethod
def create(
cls, config: MultilspyConfig, logger: MultilspyLogger, repository_root_path: str
cls,
config: MultilspyConfig,
logger: MultilspyLogger,
repository_root_path: str,
timeout: Optional[int] = None,
) -> "SyncLanguageServer":
"""
Creates a language specific LanguageServer instance based on the given configuration, and appropriate settings for the programming language.
Expand All @@ -772,7 +785,8 @@ def create(
:return SyncLanguageServer: A language specific LanguageServer instance.
"""
return SyncLanguageServer(
LanguageServer.create(config, logger, repository_root_path)
LanguageServer.create(config, logger, repository_root_path),
timeout=timeout,
)

@contextmanager
Expand Down Expand Up @@ -856,7 +870,7 @@ def request_definition(
"""
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_definition(file_path, line, column), self.loop
).result()
).result(timeout=self.timeout)
return result

def request_references(
Expand All @@ -874,7 +888,7 @@ def request_references(
"""
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_references(file_path, line, column), self.loop
).result(timeout=15)
).result(timeout=self.timeout)
return result

def request_completions(
Expand All @@ -899,7 +913,7 @@ def request_completions(
relative_file_path, line, column, allow_incomplete
),
self.loop,
).result()
).result(timeout=self.timeout)
return result

def request_document_symbols(
Expand All @@ -918,7 +932,7 @@ def request_document_symbols(
"""
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_document_symbols(relative_file_path), self.loop
).result()
).result(timeout=self.timeout)
return result

def request_hover(
Expand All @@ -937,5 +951,5 @@ def request_hover(
result = asyncio.run_coroutine_threadsafe(
self.language_server.request_hover(relative_file_path, line, column),
self.loop,
).result()
).result(timeout=self.timeout)
return result
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
from contextlib import asynccontextmanager
import logging
import os
import pathlib
from typing import AsyncIterator
from blarify.vendor.multilspy.language_server import LanguageServer
from blarify.vendor.multilspy.lsp_protocol_handler.server import ProcessLaunchInfo
import json


class DartLanguageServer(LanguageServer):
"""
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 = (
"dart language-server --client-id my-editor.my-plugin --client-version 1.2"
)
super().__init__(
config,
logger,
repository_root_path,
ProcessLaunchInfo(cmd=executable_path, cwd=repository_root_path),
"dart",
)

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

@asynccontextmanager
async def start_server(self) -> AsyncIterator["DartLanguageServer"]:
"""
Start the language server and yield when the server is ready.
"""

async def execute_client_command_handler(params):
return []

async def do_nothing(params):
return

async def check_experimental_status(params):
pass

async 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
)

async with super().start_server():
self.logger.log(
"Starting dart-language-server server process", logging.INFO
)
await 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 = await 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({})

yield self

await self.server.shutdown()
await self.server.stop()
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"_description": "This file contains the initialization parameters for the Dart Language Server.",
"processId": "$processId",
"rootPath": "$rootPath",
"rootUri": "$rootUri",
"capabilities": {},
"trace": "verbose",
"workspaceFolders": [
{
"uri": "$uri",
"name": "$name"
}
]
}

This file was deleted.

This file was deleted.

This file was deleted.

This file was deleted.

Loading