diff --git a/plugin/__init__.py b/plugin/__init__.py index 8c7643996..4ec30c111 100644 --- a/plugin/__init__.py +++ b/plugin/__init__.py @@ -33,6 +33,7 @@ from .core.protocol import ServerNotification from .core.protocol import ServerRequest from .core.protocol import ServerResponse +from .core.protocol import TextPosition from .core.registry import LspTextCommand from .core.registry import LspWindowCommand from .core.sessions import Session @@ -46,8 +47,17 @@ from .core.url import parse_uri from .core.url import uri_to_filename # deprecated from .core.version import __version__ +from .core.views import first_selection_region +from .core.views import offset_to_position +from .core.views import point_to_offset +from .core.views import position_to_offset +from .core.views import region_to_range +from .core.views import text_document_identifier +from .core.views import text_document_position_params from .core.views import uri_from_view from .core.workspace import WorkspaceFolder +from .execute_command import LspExecuteCommand +from .locationpicker import LocationPicker # This is the public API for LSP-* packages __all__ = [ @@ -65,6 +75,8 @@ 'FileWatcherEventType', 'FileWatcherProtocol', 'IsApplicableContext', + 'LocationPicker', + 'LspExecuteCommand', 'LspPlugin', 'LspTextCommand', 'LspWindowCommand', @@ -82,6 +94,7 @@ 'Session', 'SessionBufferProtocol', 'SessionViewProtocol', + 'TextPosition', 'TransportWrapper', 'WorkspaceFolder', '__version__', @@ -89,12 +102,19 @@ 'command_handler', 'css', 'filename_to_uri', + 'first_selection_region', 'matches_pattern', 'notification_handler', + 'offset_to_position', 'parse_uri', + 'point_to_offset', + 'position_to_offset', + 'region_to_range', 'register_file_watcher_implementation', 'register_plugin', 'request_handler', + 'text_document_identifier', + 'text_document_position_params', 'unregister_plugin', 'uri_from_view', 'uri_handler', diff --git a/plugin/core/protocol.py b/plugin/core/protocol.py index 221ca57f7..808d87ef8 100644 --- a/plugin/core/protocol.py +++ b/plugin/core/protocol.py @@ -2,7 +2,7 @@ from __future__ import annotations from ...protocol import * # For backward compatibility with LSP packages. # noqa: F403 -from functools import total_ordering +from dataclasses import dataclass from typing import Any from typing import Callable from typing import Generic @@ -12,6 +12,7 @@ from typing import TypedDict from typing import TypeVar from typing import Union +from typing_extensions import deprecated from typing_extensions import NotRequired from typing_extensions import TypeAlias @@ -412,31 +413,17 @@ def to_payload(self) -> NotificationMessage: return payload -@total_ordering -class Point: - def __init__(self, row: int, col: int) -> None: - self.row = int(row) - self.col = int(col) # in UTF-16 +@dataclass(frozen=True, order=True) +class TextPosition: + row: int + col: int # in UTF-16 def __repr__(self) -> str: return f"{self.row}:{self.col}" - def __eq__(self, other: object) -> bool: - if not isinstance(other, Point): - return NotImplemented - return self.row == other.row and self.col == other.col - - def __lt__(self, other: object) -> bool: - if not isinstance(other, Point): - return NotImplemented - return (self.row, self.col) < (other.row, other.col) - - def __hash__(self) -> int: - return hash(self.__repr__()) - @classmethod - def from_lsp(cls, point: Position) -> Point: - return Point(point['line'], point['character']) + def from_lsp(cls, position: Position) -> TextPosition: + return cls(position['line'], position['character']) def to_lsp(self) -> Position: return { @@ -445,6 +432,11 @@ def to_lsp(self) -> Position: } +@deprecated('Use TextPosition instead') +class Point(TextPosition): + pass + + class ResponseError(TypedDict): code: int message: str diff --git a/plugin/core/views.py b/plugin/core/views.py index 46239f6ba..6226f9709 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -50,8 +50,8 @@ from .constants import SublimeKind from .css import css as lsp_css from .protocol import Notification -from .protocol import Point from .protocol import Request +from .protocol import TextPosition from .settings import userprefs from .url import encode_code_action_uri from .url import parse_uri @@ -69,6 +69,7 @@ from typing import Iterable from typing import Sequence from typing import TYPE_CHECKING +from typing_extensions import deprecated import html import itertools import linecache @@ -182,22 +183,27 @@ def extract_variables(window: sublime.Window) -> dict[str, str]: return variables -def point_to_offset(point: Point, view: sublime.View) -> int: +def point_to_offset(point: TextPosition, view: sublime.View) -> int: # @see https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/#position # If the character value is greater than the line length it defaults back to the line length. return view.text_point_utf16(point.row, point.col, clamp_column=True) -def offset_to_point(view: sublime.View, offset: int) -> Point: - return Point(*view.rowcol_utf16(offset)) +def offset_to_point(view: sublime.View, offset: int) -> TextPosition: + return TextPosition(*view.rowcol_utf16(offset)) -def position(view: sublime.View, offset: int) -> Position: +def offset_to_position(view: sublime.View, offset: int) -> Position: return offset_to_point(view, offset).to_lsp() +@deprecated('Use offset_to_position() instead') +def position(view: sublime.View, offset: int) -> Position: + return offset_to_position(view, offset) + + def position_to_offset(position: Position, view: sublime.View) -> int: - return point_to_offset(Point.from_lsp(position), view) + return point_to_offset(TextPosition.from_lsp(position), view) def get_symbol_kind_from_scope(scope_name: str) -> SublimeKind: @@ -318,7 +324,7 @@ def versioned_text_document_identifier(view: sublime.View, version: int) -> Vers def text_document_position_params(view: sublime.View, location: int) -> TextDocumentPositionParams: - return {"textDocument": text_document_identifier(view), "position": position(view, location)} + return {"textDocument": text_document_identifier(view), "position": offset_to_position(view, location)} def did_open_text_document_params(view: sublime.View, language_id: str) -> DidOpenTextDocumentParams: @@ -451,7 +457,7 @@ def text_document_ranges_formatting( def selection_range_params(view: sublime.View) -> SelectionRangeParams: return { "textDocument": text_document_identifier(view), - "positions": [position(view, r.b) for r in view.sel()] + "positions": [offset_to_position(view, r.b) for r in view.sel()] } diff --git a/plugin/references.py b/plugin/references.py index 4ba0d18ff..50b4a4b5d 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -1,8 +1,8 @@ from __future__ import annotations from .core.constants import RegionKey -from .core.protocol import Point from .core.protocol import Request +from .core.protocol import TextPosition from .core.registry import get_position from .core.registry import LspTextCommand from .core.registry import windows @@ -181,7 +181,7 @@ def _show_references_in_quick_panel( placeholder = "References to " + word kind = get_symbol_kind_from_scope(self.view.scope_name(position)) index = 0 - locations.sort(key=lambda location: (location['uri'], Point.from_lsp(location['range']['start']))) + locations.sort(key=lambda location: (location['uri'], TextPosition.from_lsp(location['range']['start']))) if len(selection): pt = selection[0].b view_filename = self.view.file_name() @@ -244,13 +244,13 @@ def _group_locations_by_uri( window: sublime.Window, config: ClientConfig, locations: list[Location] -) -> dict[str, list[tuple[Point, str]]]: +) -> dict[str, list[tuple[TextPosition, str]]]: """Return a dictionary that groups locations by the URI it belongs.""" - grouped_locations: dict[str, list[tuple[Point, str]]] = {} + grouped_locations: dict[str, list[tuple[TextPosition, str]]] = {} for location in locations: uri, position = get_uri_and_position_from_location(location) file_path = config.map_server_uri_to_client_path(uri) - point = Point.from_lsp(position) + point = TextPosition.from_lsp(position) # get line of the reference, to showcase its use reference_line = get_line(window, file_path, point.row) if grouped_locations.get(file_path) is None: