-
Notifications
You must be signed in to change notification settings - Fork 184
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
35 changed files
with
3,902 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Minimal makefile for Sphinx documentation | ||
|
||
# You can set these variables from the command line. | ||
SOURCEDIR = api | ||
BUILDDIR = dist | ||
|
||
.PHONY: clean | ||
|
||
install: | ||
pip3 install sphinx sphinx-rtd-theme sphinx-autodoc-typehints ghp-import mkdocs mkdocs-material mkdocs-redirects | ||
# sed -i -E 's/sublime\.DRAW_[A-Z_]*/0/g' source/modules/LSP/plugin/core/views.py | ||
# sed -i -E 's/sublime\.HIDE_ON_MINIMAP/0/g' source/modules/LSP/plugin/core/views.py | ||
|
||
build: | ||
sphinx-build -M html "$(SOURCEDIR)" "$(BUILDDIR)" | ||
|
||
deploy: | ||
ghp-import --no-jekyll --push --force html | ||
|
||
clean: | ||
rm -rf doctrees html |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
from ._client_handler import ClientHandler | ||
from ._client_handler import notification_handler | ||
from ._client_handler import request_handler | ||
from .api_wrapper_interface import ApiWrapperInterface | ||
from .generic_client_handler import GenericClientHandler | ||
from .node_runtime import NodeRuntime | ||
from .npm_client_handler import NpmClientHandler | ||
from .server_npm_resource import ServerNpmResource | ||
from .server_pip_resource import ServerPipResource | ||
from .server_resource_interface import ServerResourceInterface | ||
from .server_resource_interface import ServerStatus | ||
|
||
__all__ = [ | ||
'ApiWrapperInterface', | ||
'ClientHandler', | ||
'GenericClientHandler', | ||
'NodeRuntime', | ||
'NpmClientHandler', | ||
'ServerResourceInterface', | ||
'ServerStatus', | ||
'ServerNpmResource', | ||
'ServerPipResource', | ||
'notification_handler', | ||
'request_handler', | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
from .abstract_plugin import ClientHandler | ||
from .api_decorator import notification_handler | ||
from .api_decorator import request_handler | ||
|
||
__all__ = [ | ||
'ClientHandler', | ||
'notification_handler', | ||
'request_handler', | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,155 @@ | ||
from .._util import weak_method | ||
from ..api_wrapper_interface import ApiWrapperInterface | ||
from ..server_resource_interface import ServerStatus | ||
from .api_decorator import register_decorated_handlers | ||
from .interface import ClientHandlerInterface | ||
from functools import partial | ||
from LSP.plugin import AbstractPlugin | ||
from LSP.plugin import ClientConfig | ||
from LSP.plugin import Notification | ||
from LSP.plugin import register_plugin | ||
from LSP.plugin import Request | ||
from LSP.plugin import Response | ||
from LSP.plugin import Session | ||
from LSP.plugin import unregister_plugin | ||
from LSP.plugin import WorkspaceFolder | ||
from LSP.plugin.core.rpc import method2attr | ||
from LSP.plugin.core.typing import Any, Callable, Dict, List, Optional, Tuple, TypedDict | ||
from os import path | ||
from weakref import ref | ||
import sublime | ||
|
||
__all__ = ['ClientHandler'] | ||
|
||
LanguagesDict = TypedDict('LanguagesDict', { | ||
'document_selector': Optional[str], | ||
'languageId': Optional[str], | ||
'scopes': Optional[List[str]], | ||
'syntaxes': Optional[List[str]], | ||
}, total=False) | ||
ApiNotificationHandler = Callable[[Any], None] | ||
ApiRequestHandler = Callable[[Any, Callable[[Any], None]], None] | ||
|
||
|
||
class ApiWrapper(ApiWrapperInterface): | ||
def __init__(self, plugin: 'ref[AbstractPlugin]'): | ||
self.__plugin = plugin | ||
|
||
def __session(self) -> Optional[Session]: | ||
plugin = self.__plugin() | ||
return plugin.weaksession() if plugin else None | ||
|
||
# --- ApiWrapperInterface ----------------------------------------------------------------------------------------- | ||
|
||
def on_notification(self, method: str, handler: ApiNotificationHandler) -> None: | ||
def handle_notification(weak_handler: ApiNotificationHandler, params: Any) -> None: | ||
weak_handler(params) | ||
|
||
plugin = self.__plugin() | ||
if plugin: | ||
setattr(plugin, method2attr(method), partial(handle_notification, weak_method(handler))) | ||
|
||
def on_request(self, method: str, handler: ApiRequestHandler) -> None: | ||
def send_response(request_id: Any, result: Any) -> None: | ||
session = self.__session() | ||
if session: | ||
session.send_response(Response(request_id, result)) | ||
|
||
def on_response(weak_handler: ApiRequestHandler, params: Any, request_id: Any) -> None: | ||
weak_handler(params, lambda result: send_response(request_id, result)) | ||
|
||
plugin = self.__plugin() | ||
if plugin: | ||
setattr(plugin, method2attr(method), partial(on_response, weak_method(handler))) | ||
|
||
def send_notification(self, method: str, params: Any) -> None: | ||
session = self.__session() | ||
if session: | ||
session.send_notification(Notification(method, params)) | ||
|
||
def send_request(self, method: str, params: Any, handler: Callable[[Any, bool], None]) -> None: | ||
session = self.__session() | ||
if session: | ||
session.send_request( | ||
Request(method, params), lambda result: handler(result, False), lambda result: handler(result, True)) | ||
else: | ||
handler(None, True) | ||
|
||
|
||
class ClientHandler(AbstractPlugin, ClientHandlerInterface): | ||
""" | ||
The base class for creating an LSP plugin. | ||
""" | ||
|
||
# --- AbstractPlugin handlers ------------------------------------------------------------------------------------- | ||
|
||
@classmethod | ||
def name(cls) -> str: | ||
return cls.get_displayed_name() | ||
|
||
@classmethod | ||
def configuration(cls) -> Tuple[sublime.Settings, str]: | ||
return cls.read_settings() | ||
|
||
@classmethod | ||
def additional_variables(cls) -> Dict[str, str]: | ||
return cls.get_additional_variables() | ||
|
||
@classmethod | ||
def needs_update_or_installation(cls) -> bool: | ||
if cls.manages_server(): | ||
server = cls.get_server() | ||
return bool(server and server.needs_installation()) | ||
return False | ||
|
||
@classmethod | ||
def install_or_update(cls) -> None: | ||
server = cls.get_server() | ||
if server: | ||
server.install_or_update() | ||
|
||
@classmethod | ||
def can_start(cls, window: sublime.Window, initiating_view: sublime.View, | ||
workspace_folders: List[WorkspaceFolder], configuration: ClientConfig) -> Optional[str]: | ||
if cls.manages_server(): | ||
server = cls.get_server() | ||
if not server or server.get_status() == ServerStatus.ERROR: | ||
return "{}: Error installing server dependencies.".format(cls.package_name) | ||
if server.get_status() != ServerStatus.READY: | ||
return "{}: Server installation in progress...".format(cls.package_name) | ||
message = cls.is_allowed_to_start(window, initiating_view, workspace_folders, configuration) | ||
if message: | ||
return message | ||
# Lazily update command after server has initialized if not set manually by the user. | ||
if not configuration.command: | ||
configuration.command = cls.get_command() | ||
return None | ||
|
||
@classmethod | ||
def on_pre_start(cls, window: sublime.Window, initiating_view: sublime.View, | ||
workspace_folders: List[WorkspaceFolder], configuration: ClientConfig) -> Optional[str]: | ||
extra_paths = path.pathsep.join(cls.get_additional_paths()) | ||
if extra_paths: | ||
original_path = configuration.env.get('PATH') or '' | ||
if isinstance(original_path, list): | ||
original_path = path.pathsep.join(original_path) | ||
configuration.env['PATH'] = path.pathsep.join([extra_paths, original_path]) | ||
return None | ||
|
||
# --- ClientHandlerInterface -------------------------------------------------------------------------------------- | ||
|
||
@classmethod | ||
def setup(cls) -> None: | ||
register_plugin(cls) | ||
|
||
@classmethod | ||
def cleanup(cls) -> None: | ||
unregister_plugin(cls) | ||
|
||
# --- Internals --------------------------------------------------------------------------------------------------- | ||
|
||
def __init__(self, *args: Any, **kwargs: Any) -> None: | ||
super().__init__(*args, **kwargs) | ||
api = ApiWrapper(ref(self)) # type: ignore | ||
register_decorated_handlers(self, api) | ||
self.on_ready(api) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
from ..api_wrapper_interface import ApiWrapperInterface | ||
from .interface import ClientHandlerInterface | ||
from LSP.plugin.core.typing import Any, Callable, List, Optional, TypeVar, Union | ||
import inspect | ||
|
||
__all__ = [ | ||
"notification_handler", | ||
"request_handler", | ||
"register_decorated_handlers", | ||
] | ||
|
||
T = TypeVar('T') | ||
# the first argument is always "self" | ||
NotificationHandler = Callable[[Any, Any], None] | ||
RequestHandler = Callable[[Any, Any, Callable[[Any], None]], None] | ||
MessageMethods = Union[str, List[str]] | ||
|
||
_HANDLER_MARKS = { | ||
"notification": "__handle_notification_message_methods", | ||
"request": "__handle_request_message_methods", | ||
} | ||
|
||
|
||
def notification_handler(notification_methods: MessageMethods) -> Callable[[NotificationHandler], NotificationHandler]: | ||
""" | ||
Marks the decorated function as a "notification" message handler. | ||
On server sending the notification, the decorated function will be called with the `params` argument which contains | ||
the payload. | ||
""" | ||
|
||
return _create_handler("notification", notification_methods) | ||
|
||
|
||
def request_handler(request_methods: MessageMethods) -> Callable[[RequestHandler], RequestHandler]: | ||
""" | ||
Marks the decorated function as a "request" message handler. | ||
On server sending the request, the decorated function will be called with two arguments (`params` and `respond`). | ||
The first argument (`params`) is the payload of the request and the second argument (`respond`) is the function that | ||
must be used to respond to the request. The `respond` function takes any data that should be sent back to the | ||
server. | ||
""" | ||
|
||
return _create_handler("request", request_methods) | ||
|
||
|
||
def _create_handler(client_event: str, message_methods: MessageMethods) -> Callable[[T], T]: | ||
""" Marks the decorated function as a message handler. """ | ||
|
||
message_methods = [message_methods] if isinstance(message_methods, str) else message_methods | ||
|
||
def decorator(func: T) -> T: | ||
setattr(func, _HANDLER_MARKS[client_event], message_methods) | ||
return func | ||
|
||
return decorator | ||
|
||
|
||
def register_decorated_handlers(client_handler: ClientHandlerInterface, api: ApiWrapperInterface) -> None: | ||
""" | ||
Register decorator-style custom message handlers. | ||
This method works as following: | ||
1. Scan through all methods of `client_handler`. | ||
2. If a method is decorated, it will have a "handler mark" attribute which is set by the decorator. | ||
3. Register the method with wanted message methods, which are stored in the "handler mark" attribute. | ||
:param client_handler: The instance of the client handler. | ||
:param api: The API instance for interacting with the server. | ||
""" | ||
for _, func in inspect.getmembers(client_handler, predicate=inspect.isroutine): | ||
for client_event, handler_mark in _HANDLER_MARKS.items(): | ||
message_methods = getattr(func, handler_mark, None) # type: Optional[List[str]] | ||
if message_methods is None: | ||
continue | ||
|
||
event_registrator = getattr(api, "on_" + client_event, None) | ||
if callable(event_registrator): | ||
for message_method in message_methods: | ||
event_registrator(message_method, func) | ||
|
||
# it makes no sense that a handler handles both "notification" and "request" | ||
# so we do early break once we've registered a handler | ||
break |
Oops, something went wrong.