-
Notifications
You must be signed in to change notification settings - Fork 193
Introduce LspPlugin API #2739
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
rchl
wants to merge
98
commits into
main
Choose a base branch
from
feat/new-plugin-api
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Introduce LspPlugin API #2739
Changes from 6 commits
Commits
Show all changes
98 commits
Select commit
Hold shift + click to select a range
f1b0dee
Introduce new plugin API
rchl 9a749ea
public export
rchl cb0d6c9
opt-in flag
rchl 9e40a7c
indent
rchl 70096c1
can't be abstract
rchl d996f1b
dump old code
rchl af0b028
doc
rchl c95005e
adding new apis
rchl 4013c91
blank
rchl 4aa35ee
rename to LspPlugin
rchl d6242ee
remove on_post_start
rchl 83e91ba
rename on_pre_server_command to on_execute_command
rchl 16b1653
Merge branch 'main' into feat/new-plugin-api
rchl 3173d4c
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl ea9e9c1
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl 2227561
remove name() and configuration()
rchl 15a011c
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl 4053880
storage_path as a property
rchl f35e6f3
use Path
rchl 6f843c5
auto-detect session-name of the lsp command
rchl cd25ebe
remove AbstractPlugin.selector
rchl 6637782
remove comment
rchl 62ca15f
on_execute_command returns Promise
rchl a2fbdbe
update tests
rchl 8c117c4
lint
rchl fc6f079
on_open_uri_async returns a Promise
rchl e16290b
on_open_uri_async returns a Promise
rchl d78037b
Add generic arguments
rchl 53d7856
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl 1ff861c
expose plugin_storage_path instead of storage_path
rchl 3acb880
Merge branch 'main' into feat/new-plugin-api
rchl b873a36
plugin_storage_path
rchl 05df9d6
Merge branch 'main' into feat/new-plugin-api
rchl 263524a
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl 28ddcb2
restore request_handler
rchl a51793a
Merge branch 'main' into feat/new-plugin-api
rchl 1bf417a
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl d2e334e
fixup
rchl 3a31944
fixup
rchl a404bbc
fixup
rchl bbfb5d2
Remove AbstractPlugin.should_ignore()
rchl f9df53f
Merge branch 'main' into feat/new-plugin-api
rchl 42592b3
Use ClientRequest/ClientNotification/ServerResponse/ServerNotification
rchl e28d939
Merge branch 'main' into feat/new-plugin-api
rchl 868c3b1
set lsp_uri if view
rchl beecb8b
remove unused HandleUpdateOrInstallationParams
rchl 7a20c5c
fixup
rchl 48575cc
review comments
rchl cad2247
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl 01a4a78
use Final for plugin_storage_path
rchl 5008c02
Merge branch 'main' into feat/new-plugin-api
rchl bfee983
bug
rchl 3eca3f7
Add LspPlugin.session_name and initialize on subclass
rchl dc90c3e
Remove deprecated set_window_status_async (jdtls will updated)
rchl 4b1d9c4
Replace on_pre_start with 3 separate APIs
rchl 9aa4824
replace can_start with DontStartPluginError
rchl 049a6ef
Merge branch 'main' into feat/new-plugin-api
rchl 24d3fd4
Fix after merge
rchl 723f359
change "def initialization_options" return type
rchl 309bd9e
Rename DontStartPluginError to PluginStartError
rchl cf668f2
update tooling
rchl 435a9d4
add missing super init
rchl eb7772d
decide workspace folder in "def working_directory"
rchl 3a936c4
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl d0ab4ce
formatting
rchl 315c682
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl 4f1290e
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl c172460
expand documentation
rchl bbcff02
Rename PluginContext.initiating_view to view
rchl 4c9aaef
Update plugin/api.py
rchl ab945b5
add register() and unregister() on LpsPlugin
rchl 27be495
add migration guide
rchl 3610a4c
always return dict from additional_variables
rchl c12b4a4
remove on_settings_changed
rchl ac16cee
Merge branch 'main' into feat/new-plugin-api
rchl 7764a62
add on_before_initialize
rchl c139657
compatibility with pyright
rchl a85aa94
add pass
rchl 32aacdb
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl 2494693
make PackagedTask public
rchl 1d213a9
add TransportWrapper.send_bytes()
rchl 7fdfe9e
only import ref
rchl 1a1784c
update migration
rchl be6aa2f
update migration
rchl 68f748a
update migration
rchl d787d30
deprecate AbstractPlugin
rchl 83d7d88
Merge branch 'main' into feat/new-plugin-api
rchl 0299203
deprecate un/register_plugin
rchl 9b1a834
nicer syntax
rchl f3f557d
revert docs for deprecated function
rchl 3cea9fa
don't pass PluginContext to init
rchl 6f5778b
Add doc to PluginStartError
rchl 414a4a0
rename LspPlugin.session_name to name
rchl 8e28057
store window variables in Session
rchl a8eef3c
fix test
rchl 710e05c
simplify: no need to pass plugin_data to Session
rchl af33477
add final decorator
rchl 569e2bf
Merge remote-tracking branch 'origin/main' into feat/new-plugin-api
rchl File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Some comments aren't visible on the classic Files Changed page.
There are no files selected for viewing
This file contains hidden or 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 hidden or 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,329 @@ | ||
| from __future__ import annotations | ||
| from .core.constants import ST_STORAGE_PATH | ||
| from abc import ABCMeta | ||
| from abc import abstractmethod | ||
| from typing import Any, Callable, Literal, TypedDict, final, TYPE_CHECKING | ||
| import sublime | ||
|
|
||
| if TYPE_CHECKING: | ||
| from ..protocol import ConfigurationItem | ||
| from ..protocol import DocumentUri | ||
| from ..protocol import ExecuteCommandParams | ||
| from .core.collections import DottedDict | ||
| from .core.protocol import Notification | ||
| from .core.protocol import Request | ||
| from .core.protocol import Response | ||
| from .core.sessions import Session | ||
| from .core.sessions import SessionBufferProtocol | ||
| from .core.types import ClientConfig | ||
| from .core.views import MarkdownLangMap | ||
| from .core.workspace import WorkspaceFolder | ||
| from weakref import ref | ||
|
|
||
|
|
||
| class HandleUpdateOrInstallationParams(TypedDict): | ||
| set_installing_status: Callable[[], None] | ||
|
|
||
|
|
||
| @final | ||
| class PluginContext: | ||
| def __init__( | ||
| self, | ||
| configuration: ClientConfig, | ||
| initiating_view: sublime.View, | ||
| window: sublime.Window, | ||
| workspace_folders: list[WorkspaceFolder] | ||
| ) -> None: | ||
| self.configuration = configuration | ||
| self.initiating_view = initiating_view | ||
| self.window = window | ||
| self.workspace_folders = workspace_folders | ||
|
|
||
|
|
||
| class AbstractPluginV2(metaclass=ABCMeta): | ||
| """ | ||
| TODO: doc | ||
| """ | ||
|
|
||
| API_VERSION: Literal[2] = 2 | ||
|
|
||
| @classmethod | ||
| @abstractmethod | ||
| def name(cls) -> str: | ||
| """ | ||
| A human-friendly name. If your plugin is called "LSP-foobar", then this should return "foobar". If you also | ||
| have your settings file called "LSP-foobar.sublime-settings", then you don't even need to re-implement the | ||
| configuration method (see below). | ||
| """ | ||
| raise NotImplementedError | ||
rchl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @classmethod | ||
| def configuration(cls) -> tuple[sublime.Settings, str]: | ||
| """ | ||
| Return the Settings object that defines the "command", "languages", and optionally the "initializationOptions", | ||
| "default_settings", "env" and "tcp_port" as the first element in the tuple, and the path to the base settings | ||
| filename as the second element in the tuple. | ||
rchl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| The second element in the tuple is used to handle "settings" overrides from users properly. For example, if your | ||
| plugin is called LSP-foobar, you would return "Packages/LSP-foobar/LSP-foobar.sublime-settings". | ||
|
|
||
| The "command", "initializationOptions" and "env" are subject to template string substitution. The following | ||
| template strings are recognized: | ||
|
|
||
| $file | ||
| $file_base_name | ||
| $file_extension | ||
| $file_name | ||
| $file_path | ||
| $platform | ||
| $project | ||
| $project_base_name | ||
| $project_extension | ||
| $project_name | ||
| $project_path | ||
|
|
||
| These are just the values from window.extract_variables(). Additionally, | ||
|
|
||
| $storage_path The path to the package storage (see AbstractPlugin.storage_path) | ||
| $cache_path sublime.cache_path() | ||
| $temp_dir tempfile.gettempdir() | ||
| $home os.path.expanduser('~') | ||
| $port A random free TCP-port on localhost in case "tcp_port" is set to 0. This string template can only | ||
| be used in the "command" | ||
|
|
||
| The "command" and "env" are expanded upon starting the subprocess of the Session. The "initializationOptions" | ||
| are expanded upon doing the initialize request. "initializationOptions" does not expand $port. | ||
|
|
||
| When you're managing your own server binary, you would typically place it in sublime.cache_path(). So your | ||
| "command" should look like this: "command": ["$cache_path/LSP-foobar/server_binary", "--stdio"] | ||
| """ | ||
| name = cls.name() | ||
| basename = f"LSP-{name}.sublime-settings" | ||
| filepath = f"Packages/LSP-{name}/{basename}" | ||
| return sublime.load_settings(basename), filepath | ||
|
|
||
| @classmethod | ||
| def additional_variables(cls, context: PluginContext) -> dict[str, str] | None: | ||
| """ | ||
| In addition to the above variables, add more variables here to be expanded. | ||
| """ | ||
| return None | ||
rchl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| @classmethod | ||
| def storage_path(cls) -> str: | ||
| """ | ||
| The storage path. Use this as your base directory to install server files. Its path is '$DATA/Package Storage'. | ||
| You should have an additional subdirectory preferably the same name as your plugin. For instance: | ||
rchl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| ```python | ||
| from LSP.plugin import AbstractPlugin | ||
| import os | ||
|
|
||
|
|
||
| class MyPlugin(AbstractPlugin): | ||
|
|
||
| @classmethod | ||
| def name(cls) -> str: | ||
| return "my-plugin" | ||
|
|
||
| @classmethod | ||
| def basedir(cls) -> str: | ||
| # Do everything relative to this directory | ||
| return os.path.join(cls.storage_path(), cls.name()) | ||
| ``` | ||
| """ | ||
| return ST_STORAGE_PATH | ||
|
|
||
| @classmethod | ||
| def handle_update_or_installation_async( | ||
| cls, context: PluginContext, params: HandleUpdateOrInstallationParams | ||
| ) -> None: | ||
| """Update or install the server binary if this plugin manages one. Called before server is started. | ||
|
|
||
| Make sure to call `args.set_installing_status()` before starting long-running operations to give user | ||
| a better feedback that something is happening. | ||
| """ | ||
| return | ||
|
|
||
| @classmethod | ||
| def can_start(cls, context: PluginContext) -> str | None: | ||
| """ | ||
| Determines ability to start. This is called after needs_update_or_installation and after install_or_update. | ||
| So you may assume that if you're managing your server binary, then it is already installed when this | ||
| classmethod is called. | ||
jwortmann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| :param window: The window | ||
| :param initiating_view: The initiating view | ||
| :param workspace_folders: The workspace folders | ||
| :param configuration: The configuration | ||
|
|
||
| :returns: A string describing the reason why we should not start a language server session, or None if we | ||
| should go ahead and start a session. | ||
| """ | ||
| return None | ||
|
|
||
| @classmethod | ||
| def on_pre_start(cls, context: PluginContext) -> str | None: | ||
| """ | ||
| Callback invoked just before the language server subprocess is started. This is the place to do last-minute | ||
| adjustments to your "command" or "init_options" in the passed-in "configuration" argument, or change the | ||
| order of the workspace folders. You can also choose to return a custom working directory, but consider that a | ||
| language server should not care about the working directory. | ||
rchl marked this conversation as resolved.
Outdated
Show resolved
Hide resolved
|
||
|
|
||
| :param window: The window | ||
| :param initiating_view: The initiating view | ||
| :param workspace_folders: The workspace folders, you can modify these | ||
| :param configuration: The configuration, you can modify this one | ||
|
|
||
| :returns: A desired working directory, or None if you don't care | ||
| """ | ||
| return None | ||
|
|
||
| @classmethod | ||
| def markdown_language_id_to_st_syntax_map(cls) -> MarkdownLangMap | None: | ||
| """ | ||
| Override this method to tweak the syntax highlighting of code blocks in popups from your language server. | ||
| The returned object should be a dictionary exactly in the form of mdpopup's language_map setting. | ||
|
|
||
| See: https://facelessuser.github.io/sublime-markdown-popups/settings/#mdpopupssublime_user_lang_map | ||
|
|
||
| :returns: The markdown language map, or None | ||
| """ | ||
| return None | ||
rchl marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def __init__(self, weaksession: ref[Session], context: PluginContext) -> None: | ||
| """ | ||
| Constructs a new instance. Your instance is constructed after a response to the initialize request. | ||
|
|
||
| :param weaksession: A weak reference to the Session. You can grab a strong reference through | ||
| self.weaksession(), but don't hold on to that reference. | ||
| """ | ||
| self.weaksession: ref[Session] = weaksession | ||
| self.context: PluginContext = context | ||
|
|
||
| # ------------- OLD -------------- | ||
|
|
||
| """ | ||
| Inherit from this class to handle non-standard requests and notifications. | ||
| Given a request/notification, replace the non-alphabetic characters with an underscore, and prepend it with "m_". | ||
| This will be the name of your method. | ||
| For instance, to implement the non-standard eslint/openDoc request, define the Python method | ||
|
|
||
| def m_eslint_openDoc(self, params, request_id): | ||
| session = self.weaksession() | ||
| if session: | ||
| webbrowser.open_tab(params['url']) | ||
| session.send_response(Response(request_id, None)) | ||
|
|
||
| To handle the non-standard eslint/status notification, define the Python method | ||
|
|
||
| def m_eslint_status(self, params): | ||
| pass | ||
|
|
||
| To understand how this works, see the __getattr__ method of the Session class. | ||
| """ | ||
|
|
||
| def on_settings_changed(self, settings: DottedDict) -> None: | ||
| """ | ||
| Override this method to alter the settings that are returned to the server for the | ||
| workspace/didChangeConfiguration notification and the workspace/configuration requests. | ||
|
|
||
| :param settings: The settings that the server should receive. | ||
| """ | ||
| pass | ||
|
|
||
| def on_workspace_configuration(self, params: ConfigurationItem, configuration: Any) -> Any: | ||
| """ | ||
| Override to augment configuration returned for the workspace/configuration request. | ||
|
|
||
| :param params: A ConfigurationItem for which configuration is requested. | ||
| :param configuration: The pre-resolved configuration for given params using the settings object or None. | ||
jwortmann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| :returns: The resolved configuration for given params. | ||
| """ | ||
| return configuration | ||
|
|
||
| def on_pre_server_command(self, command: ExecuteCommandParams, done_callback: Callable[[], None]) -> bool: | ||
| """ | ||
| Intercept a command that is about to be sent to the language server. | ||
|
|
||
| :param command: The payload containing a "command" and optionally "arguments". | ||
| :param done_callback: The callback that you promise to invoke when you return true. | ||
|
|
||
| :returns: True if *YOU* will handle this command plugin-side, false otherwise. You must invoke the | ||
| passed `done_callback` when you're done. | ||
| """ | ||
| return False | ||
jwortmann marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| def on_pre_send_request_async(self, request_id: int, request: Request) -> None: | ||
| """ | ||
| Notifies about a request that is about to be sent to the language server. | ||
| This API is triggered on async thread. | ||
|
|
||
| :param request_id: The request ID. | ||
| :param request: The request object. The request params can be modified by the plugin. | ||
| """ | ||
| pass | ||
|
|
||
| def on_pre_send_notification_async(self, notification: Notification) -> None: | ||
| """ | ||
| Notifies about a notification that is about to be sent to the language server. | ||
| This API is triggered on async thread. | ||
|
|
||
| :param notification: The notification object. The notification params can be modified by the plugin. | ||
| """ | ||
| pass | ||
|
|
||
| def on_server_response_async(self, method: str, response: Response) -> None: | ||
| """ | ||
| Notifies about a response message that has been received from the language server. | ||
| Only successful responses are passed to this method. | ||
|
|
||
| :param method: The method of the request. | ||
| :param response: The response object to the request. The response.result field can be modified by the | ||
| plugin, before it gets further handled by the LSP package. | ||
| """ | ||
| pass | ||
|
|
||
| def on_server_notification_async(self, notification: Notification) -> None: | ||
| """ | ||
| Notifies about a notification message that has been received from the language server. | ||
|
|
||
| :param notification: The notification object. | ||
| """ | ||
| pass | ||
|
|
||
| def on_open_uri_async(self, uri: DocumentUri, callback: Callable[[str, str, str], None]) -> bool: | ||
| """ | ||
| Called when a language server reports to open an URI. If you know how to handle this URI, then return True and | ||
| invoke the passed-in callback some time. | ||
|
|
||
| The arguments of the provided callback work as follows: | ||
|
|
||
| - The first argument is the title of the view that will be populated with the content of a new scratch view | ||
| - The second argument is the content of the view | ||
| - The third argument is the syntax to apply for the new view | ||
| """ | ||
| return False | ||
|
|
||
| def on_session_buffer_changed_async(self, session_buffer: SessionBufferProtocol) -> None: | ||
| """ | ||
| Called when the context of the session buffer has changed or a new buffer was opened. | ||
| """ | ||
| pass | ||
|
|
||
| def on_session_end_async(self, exit_code: int | None, exception: Exception | None) -> None: | ||
| """ | ||
| Notifies about the session ending (also if the session has crashed). Provides an opportunity to clean up | ||
| any stored state or delete references to the session or plugin instance that would otherwise prevent the | ||
| instance from being garbage-collected. | ||
|
|
||
| If the session hasn't crashed, a shutdown message will be send immediately | ||
| after this method returns. In this case exit_code and exception are None. | ||
| If the session has crashed, the exit_code and an optional exception are provided. | ||
|
|
||
| This API is triggered on async thread. | ||
| """ | ||
| pass | ||
|
|
||
|
|
||
This file contains hidden or 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
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.