From 0ab76166dda8c8e0beb98b481cb2310133509dfb Mon Sep 17 00:00:00 2001 From: Jack Cherng Date: Thu, 25 Apr 2024 03:06:41 +0800 Subject: [PATCH] refactor: use native types and f-string (#2459) * refactor: use native types and f-string Signed-off-by: Jack Cherng * refactor: remove unused import Signed-off-by: Jack Cherng * fix: make flake8 happier Signed-off-by: Jack Cherng * refactor: naive __all__ for boot.py Signed-off-by: Jack Cherng * Update boot.py Co-authored-by: deathaxe * chore: remove unused imports Signed-off-by: Jack Cherng * revert: rollback to str.format for long lines (>120 chars) Signed-off-by: Jack Cherng * revert: rollback intentional code alignment Signed-off-by: Jack Cherng --------- Signed-off-by: Jack Cherng Co-authored-by: deathaxe --- boot.py | 88 ++++++- plugin/code_actions.py | 121 +++++----- plugin/code_lens.py | 22 +- plugin/color.py | 5 +- plugin/completion.py | 50 ++-- plugin/core/active_request.py | 16 +- plugin/core/collections.py | 24 +- plugin/core/configurations.py | 24 +- plugin/core/constants.py | 16 +- plugin/core/css.py | 3 +- plugin/core/diagnostics_storage.py | 20 +- plugin/core/edit.py | 8 +- plugin/core/file_watcher.py | 20 +- plugin/core/input_handlers.py | 22 +- plugin/core/logging.py | 2 +- plugin/core/message_request_handler.py | 16 +- plugin/core/open.py | 19 +- plugin/core/panels.py | 24 +- plugin/core/paths.py | 14 +- plugin/core/progress.py | 25 +- plugin/core/promise.py | 12 +- plugin/core/protocol.py | 22 +- plugin/core/registry.py | 44 ++-- plugin/core/sessions.py | 302 ++++++++++++------------- plugin/core/settings.py | 26 +-- plugin/core/signature_help.py | 31 ++- plugin/core/transports.py | 66 +++--- plugin/core/tree_view.py | 29 ++- plugin/core/types.py | 132 +++++------ plugin/core/url.py | 14 +- plugin/core/views.py | 144 ++++++------ plugin/core/windows.py | 123 +++++----- plugin/core/workspace.py | 26 +-- plugin/diagnostics.py | 9 +- plugin/document_link.py | 5 +- plugin/documents.py | 88 +++---- plugin/edit.py | 12 +- plugin/execute_command.py | 16 +- plugin/folding_range.py | 29 ++- plugin/formatting.py | 28 +-- plugin/goto.py | 15 +- plugin/goto_diagnostic.py | 47 ++-- plugin/hierarchy.py | 44 ++-- plugin/hover.py | 60 ++--- plugin/inlay_hint.py | 39 ++-- plugin/locationpicker.py | 17 +- plugin/panels.py | 10 +- plugin/references.py | 54 ++--- plugin/rename.py | 24 +- plugin/save_command.py | 18 +- plugin/selection_range.py | 18 +- plugin/semantic_highlighting.py | 16 +- plugin/session_buffer.py | 84 +++---- plugin/session_view.py | 80 +++---- plugin/symbols.py | 54 ++--- plugin/tooling.py | 102 ++++----- scripts/release.py | 22 +- tests/server.py | 44 ++-- tests/setup.py | 50 ++-- tests/test_code_actions.py | 122 +++++----- tests/test_completion.py | 20 +- tests/test_configs.py | 2 +- tests/test_edit.py | 13 +- tests/test_file_watcher.py | 24 +- tests/test_mocks.py | 6 +- tests/test_server_requests.py | 8 +- tests/test_session.py | 20 +- tests/test_single_document.py | 16 +- tests/test_types.py | 3 +- 69 files changed, 1386 insertions(+), 1343 deletions(-) diff --git a/boot.py b/boot.py index cab6959c6..56ae078db 100644 --- a/boot.py +++ b/boot.py @@ -17,11 +17,9 @@ from .plugin.configuration import LspDisableLanguageServerInProjectCommand from .plugin.configuration import LspEnableLanguageServerGloballyCommand from .plugin.configuration import LspEnableLanguageServerInProjectCommand -from .plugin.core.collections import DottedDict from .plugin.core.css import load as load_css from .plugin.core.open import opening_files from .plugin.core.panels import PanelName -from .plugin.core.protocol import Error from .plugin.core.registry import LspNextDiagnosticCommand from .plugin.core.registry import LspOpenLocationCommand from .plugin.core.registry import LspPrevDiagnosticCommand @@ -86,10 +84,86 @@ from .plugin.tooling import LspOnDoubleClickCommand from .plugin.tooling import LspParseVscodePackageJson from .plugin.tooling import LspTroubleshootServerCommand -from typing import Any, Dict, List, Optional, Type +from typing import Any +__all__ = ( + "DocumentSyncListener", + "Listener", + "LspApplyDocumentEditCommand", + "LspApplyWorkspaceEditCommand", + "LspCallHierarchyCommand", + "LspClearLogPanelCommand", + "LspClearPanelCommand", + "LspCodeActionsCommand", + "LspCodeLensCommand", + "LspCollapseTreeItemCommand", + "LspColorPresentationCommand", + "LspCommitCompletionWithOppositeInsertMode", + "LspCopyToClipboardFromBase64Command", + "LspDisableLanguageServerGloballyCommand", + "LspDisableLanguageServerInProjectCommand", + "LspDocumentSymbolsCommand", + "LspDumpBufferCapabilities", + "LspDumpWindowConfigs", + "LspEnableLanguageServerGloballyCommand", + "LspEnableLanguageServerInProjectCommand", + "LspExecuteCommand", + "LspExpandSelectionCommand", + "LspExpandTreeItemCommand", + "LspFoldAllCommand", + "LspFoldCommand", + "LspFormatCommand", + "LspFormatDocumentCommand", + "LspFormatDocumentRangeCommand", + "LspGotoDiagnosticCommand", + "LspHideRenameButtonsCommand", + "LspHierarchyToggleCommand", + "LspHoverCommand", + "LspInlayHintClickCommand", + "LspNextDiagnosticCommand", + "LspOnDoubleClickCommand", + "LspOpenLinkCommand", + "LspOpenLocationCommand", + "LspParseVscodePackageJson", + "LspPrevDiagnosticCommand", + "LspRefactorCommand", + "LspResolveDocsCommand", + "LspRestartServerCommand", + "LspRunTextCommandHelperCommand", + "LspSaveAllCommand", + "LspSaveCommand", + "LspSelectCompletionCommand", + "LspSelectionAddCommand", + "LspSelectionClearCommand", + "LspSelectionSetCommand", + "LspShowDiagnosticsPanelCommand", + "LspShowScopeNameCommand", + "LspSignatureHelpNavigateCommand", + "LspSignatureHelpShowCommand", + "LspSourceActionCommand", + "LspSymbolDeclarationCommand", + "LspSymbolDefinitionCommand", + "LspSymbolImplementationCommand", + "LspSymbolReferencesCommand", + "LspSymbolRenameCommand", + "LspSymbolTypeDefinitionCommand", + "LspToggleCodeLensesCommand", + "LspToggleHoverPopupsCommand", + "LspToggleInlayHintsCommand", + "LspToggleLogPanelLinesLimitCommand", + "LspToggleServerPanelCommand", + "LspTroubleshootServerCommand", + "LspTypeHierarchyCommand", + "LspUpdateLogPanelCommand", + "LspUpdatePanelCommand", + "LspWorkspaceSymbolsCommand", + "TextChangeListener", + "plugin_loaded", + "plugin_unloaded", +) -def _get_final_subclasses(derived: List[Type], results: List[Type]) -> None: + +def _get_final_subclasses(derived: list[type], results: list[type]) -> None: for d in derived: d_subclasses = d.__subclasses__() if len(d_subclasses) > 0: @@ -99,7 +173,7 @@ def _get_final_subclasses(derived: List[Type], results: List[Type]) -> None: def _register_all_plugins() -> None: - plugin_classes: List[Type[AbstractPlugin]] = [] + plugin_classes: list[type[AbstractPlugin]] = [] _get_final_subclasses(AbstractPlugin.__subclasses__(), plugin_classes) for plugin_class in plugin_classes: try: @@ -112,6 +186,7 @@ def _register_all_plugins() -> None: def _unregister_all_plugins() -> None: from LSP.plugin.core.sessions import _plugins + _plugins.clear() client_configs.external.clear() client_configs.all.clear() @@ -132,7 +207,6 @@ def plugin_unloaded() -> None: class Listener(sublime_plugin.EventListener): - def on_exit(self) -> None: kill_all_subprocesses() @@ -187,7 +261,7 @@ def on_pre_close(self, view: sublime.View) -> None: tup[1](None) break - def on_post_window_command(self, window: sublime.Window, command_name: str, args: Optional[Dict[str, Any]]) -> None: + def on_post_window_command(self, window: sublime.Window, command_name: str, args: dict[str, Any] | None) -> None: if command_name == "show_panel": wm = windows.lookup(window) if not wm: diff --git a/plugin/code_actions.py b/plugin/code_actions.py index 6a0adc36f..a8c72b4e8 100644 --- a/plugin/code_actions.py +++ b/plugin/code_actions.py @@ -21,7 +21,7 @@ from abc import ABCMeta from abc import abstractmethod from functools import partial -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, List, Tuple, Union from typing import cast from typing_extensions import TypeGuard import sublime @@ -40,19 +40,19 @@ class CodeActionsManager: """Manager for per-location caching of code action responses.""" def __init__(self) -> None: - self._response_cache: Optional[Tuple[str, Promise[List[CodeActionsByConfigName]]]] = None - self.menu_actions_cache_key: Optional[str] = None - self.refactor_actions_cache: List[Tuple[str, CodeAction]] = [] - self.source_actions_cache: List[Tuple[str, CodeAction]] = [] + self._response_cache: tuple[str, Promise[list[CodeActionsByConfigName]]] | None = None + self.menu_actions_cache_key: str | None = None + self.refactor_actions_cache: list[tuple[str, CodeAction]] = [] + self.source_actions_cache: list[tuple[str, CodeAction]] = [] def request_for_region_async( self, view: sublime.View, region: sublime.Region, - session_buffer_diagnostics: List[Tuple[SessionBufferProtocol, List[Diagnostic]]], - only_kinds: Optional[List[CodeActionKind]] = None, + session_buffer_diagnostics: list[tuple[SessionBufferProtocol, list[Diagnostic]]], + only_kinds: list[CodeActionKind] | None = None, manual: bool = False, - ) -> Promise[List[CodeActionsByConfigName]]: + ) -> Promise[list[CodeActionsByConfigName]]: """ Requests code actions with provided diagnostics and specified region. If there are no diagnostics for given session, the request will be made with empty diagnostics list. @@ -64,7 +64,7 @@ def request_for_region_async( location_cache_key = None use_cache = not manual if use_cache: - location_cache_key = "{}#{}:{}".format(view.buffer_id(), view.change_count(), region) + location_cache_key = f"{view.buffer_id()}#{view.change_count()}:{region}" if self._response_cache: cache_key, task = self._response_cache if location_cache_key == cache_key: @@ -72,12 +72,12 @@ def request_for_region_async( else: self._response_cache = None elif only_kinds == MENU_ACTIONS_KINDS: - self.menu_actions_cache_key = "{}#{}:{}".format(view.buffer_id(), view.change_count(), region) + self.menu_actions_cache_key = f"{view.buffer_id()}#{view.change_count()}:{region}" self.refactor_actions_cache.clear() self.source_actions_cache.clear() - def request_factory(sb: SessionBufferProtocol) -> Optional[Request]: - diagnostics: List[Diagnostic] = [] + def request_factory(sb: SessionBufferProtocol) -> Request | None: + diagnostics: list[Diagnostic] = [] for diag_sb, diags in session_buffer_diagnostics: if diag_sb == sb: diagnostics = diags @@ -85,7 +85,7 @@ def request_factory(sb: SessionBufferProtocol) -> Optional[Request]: params = text_document_code_action_params(view, region, diagnostics, only_kinds, manual) return Request.codeAction(params, view) - def response_filter(sb: SessionBufferProtocol, actions: List[CodeActionOrCommand]) -> List[CodeActionOrCommand]: + def response_filter(sb: SessionBufferProtocol, actions: list[CodeActionOrCommand]) -> list[CodeActionOrCommand]: # Filter out non "quickfix" code actions unless "only_kinds" is provided. if only_kinds: code_actions = [cast(CodeAction, a) for a in actions if not is_command(a) and not a.get('disabled')] @@ -112,20 +112,20 @@ def response_filter(sb: SessionBufferProtocol, actions: List[CodeActionOrCommand return task def request_on_save_async( - self, view: sublime.View, on_save_actions: Dict[str, bool] - ) -> Promise[List[CodeActionsByConfigName]]: + self, view: sublime.View, on_save_actions: dict[str, bool] + ) -> Promise[list[CodeActionsByConfigName]]: listener = windows.listener_for_view(view) if not listener: return Promise.resolve([]) region = entire_content_region(view) session_buffer_diagnostics, _ = listener.diagnostics_intersecting_region_async(region) - def request_factory(sb: SessionBufferProtocol) -> Optional[Request]: + def request_factory(sb: SessionBufferProtocol) -> Request | None: session_kinds = get_session_kinds(sb) matching_kinds = get_matching_on_save_kinds(on_save_actions, session_kinds) if not matching_kinds: return None - diagnostics: List[Diagnostic] = [] + diagnostics: list[Diagnostic] = [] for diag_sb, diags in session_buffer_diagnostics: if diag_sb == sb: diagnostics = diags @@ -133,7 +133,7 @@ def request_factory(sb: SessionBufferProtocol) -> Optional[Request]: params = text_document_code_action_params(view, region, diagnostics, matching_kinds, manual=False) return Request.codeAction(params, view) - def response_filter(sb: SessionBufferProtocol, actions: List[CodeActionOrCommand]) -> List[CodeActionOrCommand]: + def response_filter(sb: SessionBufferProtocol, actions: list[CodeActionOrCommand]) -> list[CodeActionOrCommand]: # Filter actions returned from the session so that only matching kinds are collected. # Since older servers don't support the "context.only" property, those will return all # actions that need to be then manually filtered. @@ -146,25 +146,25 @@ def response_filter(sb: SessionBufferProtocol, actions: List[CodeActionOrCommand def _collect_code_actions_async( self, listener: AbstractViewListener, - request_factory: Callable[[SessionBufferProtocol], Optional[Request]], - response_filter: Optional[Callable[[SessionBufferProtocol, List[CodeActionOrCommand]], List[CodeActionOrCommand]]] = None, # noqa: E501 - ) -> Promise[List[CodeActionsByConfigName]]: + request_factory: Callable[[SessionBufferProtocol], Request | None], + response_filter: Callable[[SessionBufferProtocol, list[CodeActionOrCommand]], list[CodeActionOrCommand]] | None = None, # noqa: E501 + ) -> Promise[list[CodeActionsByConfigName]]: def on_response( - sb: SessionBufferProtocol, response: Union[Error, Optional[List[CodeActionOrCommand]]] + sb: SessionBufferProtocol, response: Error | list[CodeActionOrCommand] | None ) -> CodeActionsByConfigName: actions = [] if response and not isinstance(response, Error) and response_filter: actions = response_filter(sb, response) return (sb.session.config.name, actions) - tasks: List[Promise[CodeActionsByConfigName]] = [] + tasks: list[Promise[CodeActionsByConfigName]] = [] for sb in listener.session_buffers_async('codeActionProvider'): session = sb.session request = request_factory(sb) if request: response_handler = partial(on_response, sb) - task: Promise[Optional[List[CodeActionOrCommand]]] = session.send_request_task(request) + task: Promise[list[CodeActionOrCommand] | None] = session.send_request_task(request) tasks.append(task.then(response_handler)) # Return only results for non-empty lists. return Promise.all(tasks) \ @@ -174,14 +174,14 @@ def on_response( actions_manager = CodeActionsManager() -def get_session_kinds(sb: SessionBufferProtocol) -> List[CodeActionKind]: - session_kinds: Optional[List[CodeActionKind]] = sb.get_capability('codeActionProvider.codeActionKinds') +def get_session_kinds(sb: SessionBufferProtocol) -> list[CodeActionKind]: + session_kinds: list[CodeActionKind] | None = sb.get_capability('codeActionProvider.codeActionKinds') return session_kinds or [] def get_matching_on_save_kinds( - user_actions: Dict[str, bool], session_kinds: List[CodeActionKind] -) -> List[CodeActionKind]: + user_actions: dict[str, bool], session_kinds: list[CodeActionKind] +) -> list[CodeActionKind]: """ Filters user-enabled or disabled actions so that only ones matching the session kinds are returned. Returned kinds are those that are enabled and are not overridden by more @@ -206,7 +206,7 @@ def get_matching_on_save_kinds( return matching_kinds -def kinds_include_kind(kinds: List[CodeActionKind], kind: Optional[CodeActionKind]) -> bool: +def kinds_include_kind(kinds: list[CodeActionKind], kind: CodeActionKind | None) -> bool: """ The "kinds" include "kind" if "kind" matches one of the "kinds" exactly or one of the "kinds" is a prefix of the whole "kind" (where prefix must be followed by a dot). @@ -234,7 +234,7 @@ def is_applicable(cls, view: sublime.View) -> bool: return bool(view.window()) and bool(cls._get_code_actions_on_save(view)) @classmethod - def _get_code_actions_on_save(cls, view: sublime.View) -> Dict[str, bool]: + def _get_code_actions_on_save(cls, view: sublime.View) -> dict[str, bool]: view_code_actions = cast(Dict[str, bool], view.settings().get('lsp_code_actions_on_save') or {}) code_actions = userprefs().lsp_code_actions_on_save.copy() code_actions.update(view_code_actions) @@ -253,11 +253,11 @@ def _request_code_actions_async(self) -> None: on_save_actions = self._get_code_actions_on_save(self._task_runner.view) actions_manager.request_on_save_async(self._task_runner.view, on_save_actions).then(self._handle_response_async) - def _handle_response_async(self, responses: List[CodeActionsByConfigName]) -> None: + def _handle_response_async(self, responses: list[CodeActionsByConfigName]) -> None: if self._cancelled: return view = self._task_runner.view - tasks: List[Promise] = [] + tasks: list[Promise] = [] for config_name, code_actions in responses: session = self._task_runner.session_by_name(config_name, 'codeActionProvider') if session: @@ -276,9 +276,9 @@ class LspCodeActionsCommand(LspTextCommand): def is_visible( self, - event: Optional[dict] = None, - point: Optional[int] = None, - only_kinds: Optional[List[CodeActionKind]] = None + event: dict | None = None, + point: int | None = None, + only_kinds: list[CodeActionKind] | None = None ) -> bool: if self.applies_to_context_menu(event): return self.is_enabled(event, point) @@ -287,16 +287,16 @@ def is_visible( def run( self, edit: sublime.Edit, - event: Optional[dict] = None, - only_kinds: Optional[List[CodeActionKind]] = None, - code_actions_by_config: Optional[List[CodeActionsByConfigName]] = None + event: dict | None = None, + only_kinds: list[CodeActionKind] | None = None, + code_actions_by_config: list[CodeActionsByConfigName] | None = None ) -> None: if code_actions_by_config: self._handle_code_actions(code_actions_by_config, run_first=True) return self._run_async(only_kinds) - def _run_async(self, only_kinds: Optional[List[CodeActionKind]] = None) -> None: + def _run_async(self, only_kinds: list[CodeActionKind] | None = None) -> None: view = self.view region = first_selection_region(view) if region is None: @@ -309,9 +309,9 @@ def _run_async(self, only_kinds: Optional[List[CodeActionKind]] = None) -> None: .request_for_region_async(view, covering, session_buffer_diagnostics, only_kinds, manual=True) \ .then(lambda actions: sublime.set_timeout(lambda: self._handle_code_actions(actions))) - def _handle_code_actions(self, response: List[CodeActionsByConfigName], run_first: bool = False) -> None: + def _handle_code_actions(self, response: list[CodeActionsByConfigName], run_first: bool = False) -> None: # Flatten response to a list of (config_name, code_action) tuples. - actions: List[Tuple[ConfigName, CodeActionOrCommand]] = [] + actions: list[tuple[ConfigName, CodeActionOrCommand]] = [] for config_name, session_actions in response: actions.extend([(config_name, action) for action in session_actions]) if actions: @@ -324,7 +324,7 @@ def _handle_code_actions(self, response: List[CodeActionsByConfigName], run_firs if window: window.status_message("No code actions available") - def _show_code_actions(self, actions: List[Tuple[ConfigName, CodeActionOrCommand]]) -> None: + def _show_code_actions(self, actions: list[tuple[ConfigName, CodeActionOrCommand]]) -> None: window = self.view.window() if not window: return @@ -335,7 +335,7 @@ def _show_code_actions(self, actions: List[Tuple[ConfigName, CodeActionOrCommand selected_index=selected_index, placeholder="Code action") - def _handle_select(self, index: int, actions: List[Tuple[ConfigName, CodeActionOrCommand]]) -> None: + def _handle_select(self, index: int, actions: list[tuple[ConfigName, CodeActionOrCommand]]) -> None: if index == -1: return @@ -350,7 +350,7 @@ def run_async() -> None: def _handle_response_async(self, session_name: str, response: Any) -> None: if isinstance(response, Error): - sublime.error_message("{}: {}".format(session_name, str(response))) + sublime.error_message(f"{session_name}: {str(response)}") # This command must be a WindowCommand in order to reliably hide corresponding menu entries when no view has focus. @@ -361,26 +361,26 @@ class LspMenuActionCommand(LspWindowCommand, metaclass=ABCMeta): @property @abstractmethod - def actions_cache(self) -> List[Tuple[str, CodeAction]]: + def actions_cache(self) -> list[tuple[str, CodeAction]]: ... @property - def view(self) -> Optional[sublime.View]: + def view(self) -> sublime.View | None: return self.window.active_view() - def is_enabled(self, index: int, event: Optional[dict] = None) -> bool: + def is_enabled(self, index: int, event: dict | None = None) -> bool: if not -1 < index < len(self.actions_cache): return False return self._has_session(event) - def is_visible(self, index: int, event: Optional[dict] = None) -> bool: + def is_visible(self, index: int, event: dict | None = None) -> bool: if index == -1: if self._has_session(event): sublime.set_timeout_async(partial(self._request_menu_actions_async, event)) return False return index < len(self.actions_cache) and self._is_cache_valid(event) - def _has_session(self, event: Optional[dict] = None) -> bool: + def _has_session(self, event: dict | None = None) -> bool: view = self.view if not view: return False @@ -392,17 +392,17 @@ def _has_session(self, event: Optional[dict] = None) -> bool: return False return bool(listener.session_async(self.capability, region.b)) - def description(self, index: int, event: Optional[dict] = None) -> Optional[str]: + def description(self, index: int, event: dict | None = None) -> str | None: if -1 < index < len(self.actions_cache): return self.actions_cache[index][1]['title'] def want_event(self) -> bool: return True - def run(self, index: int, event: Optional[dict] = None) -> None: + def run(self, index: int, event: dict | None = None) -> None: sublime.set_timeout_async(partial(self.run_async, index, event)) - def run_async(self, index: int, event: Optional[dict]) -> None: + def run_async(self, index: int, event: dict | None) -> None: if self._is_cache_valid(event): config_name, action = self.actions_cache[index] session = self.session_by_name(config_name) @@ -412,19 +412,18 @@ def run_async(self, index: int, event: Optional[dict]) -> None: def _handle_response_async(self, session_name: str, response: Any) -> None: if isinstance(response, Error): - sublime.error_message("{}: {}".format(session_name, str(response))) + sublime.error_message(f"{session_name}: {str(response)}") - def _is_cache_valid(self, event: Optional[dict]) -> bool: + def _is_cache_valid(self, event: dict | None) -> bool: view = self.view if not view: return False region = self._get_region(event) if region is None: return False - return actions_manager.menu_actions_cache_key == "{}#{}:{}".format( - view.buffer_id(), view.change_count(), region) + return actions_manager.menu_actions_cache_key == f"{view.buffer_id()}#{view.change_count()}:{region}" - def _get_region(self, event: Optional[dict]) -> Optional[sublime.Region]: + def _get_region(self, event: dict | None) -> sublime.Region | None: view = self.view if not view: return None @@ -433,10 +432,10 @@ def _get_region(self, event: Optional[dict]) -> Optional[sublime.Region]: return first_selection_region(view) @staticmethod - def applies_to_context_menu(event: Optional[dict]) -> bool: + def applies_to_context_menu(event: dict | None) -> bool: return event is not None and 'x' in event - def _request_menu_actions_async(self, event: Optional[dict]) -> None: + def _request_menu_actions_async(self, event: dict | None) -> None: view = self.view if not view: return @@ -448,12 +447,12 @@ def _request_menu_actions_async(self, event: Optional[dict]) -> None: class LspRefactorCommand(LspMenuActionCommand): @property - def actions_cache(self) -> List[Tuple[str, CodeAction]]: + def actions_cache(self) -> list[tuple[str, CodeAction]]: return actions_manager.refactor_actions_cache class LspSourceActionCommand(LspMenuActionCommand): @property - def actions_cache(self) -> List[Tuple[str, CodeAction]]: + def actions_cache(self) -> list[tuple[str, CodeAction]]: return actions_manager.source_actions_cache diff --git a/plugin/code_lens.py b/plugin/code_lens.py index 38be9e726..d38788dae 100644 --- a/plugin/code_lens.py +++ b/plugin/code_lens.py @@ -10,7 +10,7 @@ from .core.views import range_to_region from html import escape as html_escape from functools import partial -from typing import Dict, Generator, Iterable, List, Optional, Tuple, Union +from typing import Generator, Iterable from typing import cast import itertools import sublime @@ -20,7 +20,7 @@ class LspToggleCodeLensesCommand(LspWindowCommand): capability = 'codeLensProvider' @classmethod - def are_enabled(cls, window: Optional[sublime.Window]) -> bool: + def are_enabled(cls, window: sublime.Window | None) -> bool: if not window: return False return bool(window.settings().get(CODE_LENS_ENABLED_KEY, True)) @@ -63,7 +63,7 @@ def __init__(self, data: CodeLens, view: sublime.View, session_name: str) -> Non self.is_resolve_error = False def __repr__(self) -> str: - return 'CodeLensData(resolved={0}, region={1!r})'.format(self.is_resolved(), self.region) + return f'CodeLensData(resolved={self.is_resolved()}, region={self.region!r})' def is_resolved(self) -> bool: """A code lens is considered resolved if the inner data contains the 'command' key.""" @@ -76,7 +76,7 @@ def to_lsp(self) -> CodeLensExtended: @property def small_html(self) -> str: - return '{}'.format(self.annotation) + return f'{self.annotation}' def resolve_annotation(self, view_id: int) -> None: command = self.data.get('command') @@ -93,7 +93,7 @@ def resolve_annotation(self, view_id: int) -> None: else: self.annotation = '...' - def resolve(self, view: sublime.View, code_lens_or_error: Union[CodeLens, Error]) -> None: + def resolve(self, view: sublime.View, code_lens_or_error: CodeLens | Error) -> None: if isinstance(code_lens_or_error, Error): self.is_resolve_error = True self.annotation = 'error' @@ -110,7 +110,7 @@ def __init__(self, view: sublime.View) -> None: self.view = view self._init = False self._phantom = sublime.PhantomSet(view, self.CODE_LENS_KEY) - self._code_lenses: Dict[Tuple[int, int], List[CodeLensData]] = {} + self._code_lenses: dict[tuple[int, int], list[CodeLensData]] = {} def clear(self) -> None: self._code_lenses.clear() @@ -126,17 +126,17 @@ def _clear_annotations(self) -> None: self.view.erase_regions(self._region_key(lens.session_name, index)) def _region_key(self, session_name: str, index: int) -> str: - return '{0}.{1}.{2}'.format(self.CODE_LENS_KEY, session_name, index) + return f'{self.CODE_LENS_KEY}.{session_name}.{index}' def clear_view(self) -> None: self._phantom.update([]) self._clear_annotations() - def handle_response(self, session_name: str, response: List[CodeLens]) -> None: + def handle_response(self, session_name: str, response: list[CodeLens]) -> None: self._init = True responses = [CodeLensData(data, self.view, session_name) for data in response] responses.sort(key=lambda c: c.region) - result: Dict[Tuple[int, int], List[CodeLensData]] = { + result: dict[tuple[int, int], list[CodeLensData]] = { region.to_tuple(): list(groups) for region, groups in itertools.groupby(responses, key=lambda c: c.region) } @@ -217,7 +217,7 @@ def run(self, edit: sublime.Edit) -> None: listener = windows.listener_for_view(self.view) if not listener: return - code_lenses: List[CodeLensExtended] = [] + code_lenses: list[CodeLensExtended] = [] for region in self.view.sel(): for sv in listener.session_views_async(): code_lenses.extend(sv.get_resolved_code_lenses_for_region(region)) @@ -240,7 +240,7 @@ def run(self, edit: sublime.Edit) -> None: lambda i: self.on_select(code_lenses, i) ) - def on_select(self, code_lenses: List[CodeLensExtended], index: int) -> None: + def on_select(self, code_lenses: list[CodeLensExtended], index: int) -> None: try: code_lens = code_lenses[index] except IndexError: diff --git a/plugin/color.py b/plugin/color.py index e1151d253..ef6feb9eb 100644 --- a/plugin/color.py +++ b/plugin/color.py @@ -7,7 +7,6 @@ from .core.registry import LspTextCommand from .core.views import range_to_region from .core.views import text_document_identifier -from typing import List import sublime @@ -30,7 +29,7 @@ def run(self, edit: sublime.Edit, color_information: ColorInformation) -> None: def want_event(self) -> bool: return False - def _handle_response_async(self, response: List[ColorPresentation]) -> None: + def _handle_response_async(self, response: list[ColorPresentation]) -> None: if not response: return window = self.view.window() @@ -39,7 +38,7 @@ def _handle_response_async(self, response: List[ColorPresentation]) -> None: if self._version != self.view.change_count(): return old_text = self.view.substr(range_to_region(self._range, self.view)) - self._filtered_response: List[ColorPresentation] = [] + self._filtered_response: list[ColorPresentation] = [] for item in response: # Filter out items that would apply no change text_edit = item.get('textEdit') diff --git a/plugin/completion.py b/plugin/completion.py index 6a21c7932..1f59451e6 100644 --- a/plugin/completion.py +++ b/plugin/completion.py @@ -27,7 +27,7 @@ from .core.views import show_lsp_popup from .core.views import text_document_position_params from .core.views import update_lsp_popup -from typing import Any, Callable, Dict, Generator, List, Optional, Tuple, Union +from typing import Any, Callable, Generator, List, Tuple, Union from typing import cast from typing_extensions import TypeAlias, TypeGuard import functools @@ -59,13 +59,13 @@ def format_completion( lsp_detail = (item.get('detail') or "").replace("\n", " ") completion_kind = item.get('kind') kind = COMPLETION_KINDS.get(completion_kind, sublime.KIND_AMBIGUOUS) if completion_kind else sublime.KIND_AMBIGUOUS - details: List[str] = [] + details: list[str] = [] if can_resolve_completion_items or item.get('documentation'): # Not using "make_command_link" in a hot path to avoid slow json.dumps. args = '{{"view_id":{},"command":"lsp_resolve_docs","args":{{"index":{},"session_name":"{}"}}}}'.format( view_id, index, session_name) - href = 'subl:lsp_run_text_command_helper {}'.format(args) - details.append("More".format(href)) + href = f'subl:lsp_run_text_command_helper {args}' + details.append(f"More") if lsp_label_detail and (lsp_label + lsp_label_detail).startswith(lsp_filter_text): if lsp_label_detail[0].isalnum() and lsp_label.startswith(lsp_filter_text): # labelDetails.detail is likely a type annotation @@ -97,12 +97,12 @@ def format_completion( insert_mode = userprefs().completion_insert_mode oposite_insert_mode = 'Replace' if insert_mode == 'insert' else 'Insert' command_url = "subl:lsp_commit_completion_with_opposite_insert_mode" - details.append("{}".format(command_url, oposite_insert_mode)) + details.append(f"{oposite_insert_mode}") completion = sublime.CompletionItem( trigger, annotation, # Not using "sublime.format_command" in a hot path to avoid slow json.dumps. - 'lsp_select_completion {{"index":{},"session_name":"{}"}}'.format(index, session_name), + f'lsp_select_completion {{"index":{index},"session_name":"{session_name}"}}', sublime.COMPLETION_FORMAT_COMMAND, kind, details=" | ".join(details) @@ -112,7 +112,7 @@ def format_completion( return completion -def get_text_edit_range(text_edit: Union[TextEdit, InsertReplaceEdit]) -> Range: +def get_text_edit_range(text_edit: TextEdit | InsertReplaceEdit) -> Range: if 'insert' in text_edit and 'replace' in text_edit: text_edit = cast(InsertReplaceEdit, text_edit) insert_mode = userprefs().completion_insert_mode @@ -135,7 +135,7 @@ def completion_with_defaults(item: CompletionItem, item_defaults: CompletionItem """ Currently supports defaults for: ["editRange", "insertTextFormat", "data"] """ if not item_defaults: return item - default_text_edit: Optional[Union[TextEdit, InsertReplaceEdit]] = None + default_text_edit: TextEdit | InsertReplaceEdit | None = None edit_range = item_defaults.get('editRange') if edit_range: # If textEditText is not provided and a list's default range is provided @@ -178,16 +178,16 @@ def __init__( view: sublime.View, location: int, triggered_manually: bool, - on_done_async: Callable[[List[sublime.CompletionItem], int], None] + on_done_async: Callable[[list[sublime.CompletionItem], int], None] ) -> None: self._view = view self._location = location self._triggered_manually = triggered_manually self._on_done_async = on_done_async self._resolved = False - self._pending_completion_requests: Dict[int, weakref.ref[Session]] = {} + self._pending_completion_requests: dict[int, weakref.ref[Session]] = {} - def query_completions_async(self, sessions: List[Session]) -> None: + def query_completions_async(self, sessions: list[Session]) -> None: promises = [self._create_completion_request_async(session) for session in sessions] Promise.all(promises).then(lambda response: self._resolve_completions_async(response)) @@ -205,13 +205,13 @@ def _on_completion_response_async( self._pending_completion_requests.pop(request_id, None) return (response, weak_session) - def _resolve_completions_async(self, responses: List[ResolvedCompletions]) -> None: + def _resolve_completions_async(self, responses: list[ResolvedCompletions]) -> None: if self._resolved: return LspSelectCompletionCommand.completions = {} - items: List[sublime.CompletionItem] = [] + items: list[sublime.CompletionItem] = [] item_defaults: CompletionItemDefaults = {} - errors: List[Error] = [] + errors: list[Error] = [] flags = 0 prefs = userprefs() if prefs.inhibit_snippet_completions: @@ -228,7 +228,7 @@ def _resolve_completions_async(self, responses: List[ResolvedCompletions]) -> No session = weak_session() if not session: continue - response_items: List[CompletionItem] = [] + response_items: list[CompletionItem] = [] if isinstance(response, dict): response_items = response["items"] or [] item_defaults = response.get('itemDefaults') or {} @@ -249,7 +249,7 @@ def _resolve_completions_async(self, responses: List[ResolvedCompletions]) -> No flags |= sublime.INHIBIT_REORDER if errors: error_messages = ", ".join(str(error) for error in errors) - sublime.status_message('Completion error: {}'.format(error_messages)) + sublime.status_message(f'Completion error: {error_messages}') self._resolve_task_async(items, flags) def cancel_async(self) -> None: @@ -263,7 +263,7 @@ def _cancel_pending_requests_async(self) -> None: session.cancel_request(request_id, False) self._pending_completion_requests.clear() - def _resolve_task_async(self, completions: List[sublime.CompletionItem], flags: int = 0) -> None: + def _resolve_task_async(self, completions: list[sublime.CompletionItem], flags: int = 0) -> None: if not self._resolved: self._resolved = True self._on_done_async(completions, flags) @@ -271,7 +271,7 @@ def _resolve_task_async(self, completions: List[sublime.CompletionItem], flags: class LspResolveDocsCommand(LspTextCommand): - def run(self, edit: sublime.Edit, index: int, session_name: str, event: Optional[dict] = None) -> None: + def run(self, edit: sublime.Edit, index: int, session_name: str, event: dict | None = None) -> None: def run_async() -> None: items, item_defaults = LspSelectCompletionCommand.completions[session_name] @@ -287,7 +287,7 @@ def run_async() -> None: sublime.set_timeout_async(run_async) - def _handle_resolve_response_async(self, language_map: Optional[MarkdownLangMap], item: CompletionItem) -> None: + def _handle_resolve_response_async(self, language_map: MarkdownLangMap | None, item: CompletionItem) -> None: detail = "" documentation = "" if item: @@ -299,7 +299,7 @@ def _handle_resolve_response_async(self, language_map: Optional[MarkdownLangMap] documentation = self._format_documentation(markdown, None) minihtml_content = "" if detail: - minihtml_content += "
{}
".format(detail) + minihtml_content += f"
{detail}
" if documentation: minihtml_content += documentation @@ -320,8 +320,8 @@ def run_main() -> None: def _format_documentation( self, - content: Union[MarkedString, MarkupContent], - language_map: Optional[MarkdownLangMap] + content: MarkedString | MarkupContent, + language_map: MarkdownLangMap | None ) -> str: return minihtml(self.view, content, FORMAT_STRING | FORMAT_MARKUP_CONTENT, language_map) @@ -332,7 +332,7 @@ def _on_navigate(self, url: str) -> None: class LspCommitCompletionWithOppositeInsertMode(LspTextCommand): active = False - def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None: + def run(self, edit: sublime.Edit, event: dict | None = None) -> None: LspCommitCompletionWithOppositeInsertMode.active = True self.view.run_command("commit_completion") LspCommitCompletionWithOppositeInsertMode.active = False @@ -340,7 +340,7 @@ def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None: class LspSelectCompletionCommand(LspTextCommand): - completions: Dict[SessionName, CompletionsStore] = {} + completions: dict[SessionName, CompletionsStore] = {} def run(self, edit: sublime.Edit, index: int, session_name: str) -> None: items, item_defaults = LspSelectCompletionCommand.completions[session_name] @@ -377,7 +377,7 @@ def _on_resolved(self, session_name: str, item: CompletionItem) -> None: apply_text_edits(self.view, additional_edits) command = item.get("command") if command: - debug('Running server command "{}" for view {}'.format(command, self.view.id())) + debug(f'Running server command "{command}" for view {self.view.id()}') args = { "command_name": command["command"], "command_args": command.get("arguments"), diff --git a/plugin/core/active_request.py b/plugin/core/active_request.py index 7426174ec..1cd6ae2a8 100644 --- a/plugin/core/active_request.py +++ b/plugin/core/active_request.py @@ -4,7 +4,7 @@ from .progress import ViewProgressReporter from .progress import WindowProgressReporter from .protocol import Request -from typing import Any, Dict, Optional +from typing import Any from weakref import ref import sublime @@ -19,7 +19,7 @@ def __init__(self, sv: SessionViewProtocol, request_id: int, request: Request) - self.weaksv = ref(sv) self.request_id = request_id self.request = request - self.progress: Optional[ProgressReporter] = None + self.progress: ProgressReporter | None = None # `request.progress` is either a boolean or a string. If it's a boolean, then that signals that the server does # not support client-initiated progress. However, for some requests we still want to notify some kind of # progress to the end-user. This is communicated by the boolean value being "True". @@ -47,20 +47,20 @@ def show() -> None: def _start_progress_reporter_async( self, title: str, - message: Optional[str] = None, - percentage: Optional[float] = None - ) -> Optional[ProgressReporter]: + message: str | None = None, + percentage: float | None = None + ) -> ProgressReporter | None: sv = self.weaksv() if not sv: return None if self.request.view is not None: - key = "lspprogressview-{}-{}-{}".format(sv.session.config.name, self.request.view.id(), self.request_id) + key = f"lspprogressview-{sv.session.config.name}-{self.request.view.id()}-{self.request_id}" return ViewProgressReporter(self.request.view, key, title, message, percentage) else: - key = "lspprogresswindow-{}-{}-{}".format(sv.session.config.name, sv.session.window.id(), self.request_id) + key = f"lspprogresswindow-{sv.session.config.name}-{sv.session.window.id()}-{self.request_id}" return WindowProgressReporter(sv.session.window, key, title, message, percentage) - def update_progress_async(self, params: Dict[str, Any]) -> None: + def update_progress_async(self, params: dict[str, Any]) -> None: value = params['value'] kind = value['kind'] message = value.get("message") diff --git a/plugin/core/collections.py b/plugin/core/collections.py index 5412dd431..7e843e148 100644 --- a/plugin/core/collections.py +++ b/plugin/core/collections.py @@ -3,7 +3,7 @@ """ from __future__ import annotations from copy import deepcopy -from typing import Any, Dict, Generator, Optional +from typing import Any, Generator import sublime @@ -11,24 +11,24 @@ class DottedDict: __slots__ = ('_d',) - def __init__(self, d: Optional[Dict[str, Any]] = None) -> None: + def __init__(self, d: dict[str, Any] | None = None) -> None: """ Construct a DottedDict, optionally from an existing dictionary. :param d: An existing dictionary. """ - self._d: Dict[str, Any] = {} + self._d: dict[str, Any] = {} if d is not None: self.update(d) @classmethod - def from_base_and_override(cls, base: "DottedDict", override: Optional[Dict[str, Any]]) -> "DottedDict": + def from_base_and_override(cls, base: DottedDict, override: dict[str, Any] | None) -> DottedDict: result = DottedDict(base.copy()) if override: result.update(override) return result - def get(self, path: Optional[str] = None) -> Any: + def get(self, path: str | None = None) -> Any: """ Get a value from the dictionary. @@ -95,7 +95,7 @@ def remove(self, path: str) -> None: current = next_current current.pop(keys[-1], None) - def copy(self, path: Optional[str] = None) -> Any: + def copy(self, path: str | None = None) -> Any: """ Get a copy of the value from the dictionary or copy of whole dictionary. @@ -126,7 +126,7 @@ def clear(self) -> None: """ self._d.clear() - def assign(self, d: Dict[str, Any]) -> None: + def assign(self, d: dict[str, Any]) -> None: """ Overwrites the old stored dictionary with a fresh new dictionary. @@ -134,7 +134,7 @@ def assign(self, d: Dict[str, Any]) -> None: """ self._d = d - def update(self, d: Dict[str, Any]) -> None: + def update(self, d: dict[str, Any]) -> None: """ Overwrite and/or add new key-value pairs to the collection. @@ -146,7 +146,7 @@ def update(self, d: Dict[str, Any]) -> None: else: self.set(key, value) - def get_resolved(self, variables: Dict[str, str]) -> Dict[str, Any]: + def get_resolved(self, variables: dict[str, str]) -> dict[str, Any]: """ Resolve a DottedDict that may potentially contain template variables like $folder. @@ -156,18 +156,18 @@ def get_resolved(self, variables: Dict[str, str]) -> Dict[str, Any]: """ return sublime.expand_variables(self._d, variables) - def _update_recursive(self, current: Dict[str, Any], prefix: str) -> None: + def _update_recursive(self, current: dict[str, Any], prefix: str) -> None: if not current or any(filter(lambda key: isinstance(key, str) and (":" in key or "/" in key), current.keys())): return self.set(prefix, current) for key, value in current.items(): - path = "{}.{}".format(prefix, key) + path = f"{prefix}.{key}" if isinstance(value, dict): self._update_recursive(value, path) else: self.set(path, value) def __repr__(self) -> str: - return "{}({})".format(self.__class__.__name__, repr(self._d)) + return f"{self.__class__.__name__}({repr(self._d)})" def __eq__(self, other: Any) -> bool: if not isinstance(other, DottedDict): diff --git a/plugin/core/configurations.py b/plugin/core/configurations.py index bd4b198fa..3a618b2bb 100644 --- a/plugin/core/configurations.py +++ b/plugin/core/configurations.py @@ -9,7 +9,7 @@ from abc import abstractmethod from collections import deque from datetime import datetime, timedelta -from typing import Deque, Dict, Generator, List, Optional, Set +from typing import Generator from weakref import WeakSet import sublime @@ -21,24 +21,24 @@ class WindowConfigChangeListener(metaclass=ABCMeta): @abstractmethod - def on_configs_changed(self, config_name: Optional[str] = None) -> None: + def on_configs_changed(self, config_name: str | None = None) -> None: raise NotImplementedError() -class WindowConfigManager(object): - def __init__(self, window: sublime.Window, global_configs: Dict[str, ClientConfig]) -> None: +class WindowConfigManager: + def __init__(self, window: sublime.Window, global_configs: dict[str, ClientConfig]) -> None: self._window = window self._global_configs = global_configs - self._disabled_for_session: Set[str] = set() - self._crashes: Dict[str, Deque[datetime]] = {} - self.all: Dict[str, ClientConfig] = {} + self._disabled_for_session: set[str] = set() + self._crashes: dict[str, deque[datetime]] = {} + self.all: dict[str, ClientConfig] = {} self._change_listeners: WeakSet[WindowConfigChangeListener] = WeakSet() self._reload_configs(notify_listeners=False) def add_change_listener(self, listener: WindowConfigChangeListener) -> None: self._change_listeners.add(listener) - def get_configs(self) -> List[ClientConfig]: + def get_configs(self) -> list[ClientConfig]: return sorted(self.all.values(), key=lambda config: config.name) def match_view(self, view: sublime.View, include_disabled: bool = False) -> Generator[ClientConfig, None, None]: @@ -59,10 +59,10 @@ def match_view(self, view: sublime.View, include_disabled: bool = False) -> Gene except (IndexError, RuntimeError): pass - def update(self, updated_config_name: Optional[str] = None) -> None: + def update(self, updated_config_name: str | None = None) -> None: self._reload_configs(updated_config_name, notify_listeners=True) - def _reload_configs(self, updated_config_name: Optional[str] = None, notify_listeners: bool = False) -> None: + def _reload_configs(self, updated_config_name: str | None = None, notify_listeners: bool = False) -> None: project_data = self._window.project_data() project_settings = project_data.get("settings", {}).get("LSP", {}) if isinstance(project_data, dict) else {} if updated_config_name is None: @@ -85,7 +85,7 @@ def _reload_configs(self, updated_config_name: Optional[str] = None, notify_list try: self.all[name] = ClientConfig.from_dict(name, c) except Exception as ex: - exception_log("failed to load project-only configuration {}".format(name), ex) + exception_log(f"failed to load project-only configuration {name}", ex) if notify_listeners: for listener in self._change_listeners: listener.on_configs_changed(updated_config_name) @@ -102,7 +102,7 @@ def disable_config(self, config_name: str, only_for_session: bool = False) -> No disable_in_project(self._window, config_name) self.update(config_name) - def record_crash(self, config_name: str, exit_code: int, exception: Optional[Exception]) -> bool: + def record_crash(self, config_name: str, exit_code: int, exception: Exception | None) -> bool: """ Signal that a session has crashed. diff --git a/plugin/core/constants.py b/plugin/core/constants.py index caed44eda..a83f345a4 100644 --- a/plugin/core/constants.py +++ b/plugin/core/constants.py @@ -4,7 +4,7 @@ from .protocol import DiagnosticSeverity from .protocol import DocumentHighlightKind from .protocol import SymbolKind -from typing import Dict, Tuple +from typing import Tuple import sublime @@ -72,7 +72,7 @@ KIND_REFACTOR = (sublime.KIND_ID_COLOR_CYANISH, "r", "Refactor") KIND_SOURCE = (sublime.KIND_ID_COLOR_PURPLISH, "s", "Source") -COMPLETION_KINDS: Dict[CompletionItemKind, SublimeKind] = { +COMPLETION_KINDS: dict[CompletionItemKind, SublimeKind] = { CompletionItemKind.Text: KIND_TEXT, CompletionItemKind.Method: KIND_METHOD, CompletionItemKind.Function: KIND_FUNCTION, @@ -100,7 +100,7 @@ CompletionItemKind.TypeParameter: KIND_TYPEPARAMETER } -SYMBOL_KINDS: Dict[SymbolKind, SublimeKind] = { +SYMBOL_KINDS: dict[SymbolKind, SublimeKind] = { SymbolKind.File: KIND_FILE, SymbolKind.Module: KIND_MODULE, SymbolKind.Namespace: KIND_NAMESPACE, @@ -129,21 +129,21 @@ SymbolKind.TypeParameter: KIND_TYPEPARAMETER } -DIAGNOSTIC_KINDS: Dict[DiagnosticSeverity, SublimeKind] = { +DIAGNOSTIC_KINDS: dict[DiagnosticSeverity, SublimeKind] = { DiagnosticSeverity.Error: KIND_ERROR, DiagnosticSeverity.Warning: KIND_WARNING, DiagnosticSeverity.Information: KIND_INFORMATION, DiagnosticSeverity.Hint: KIND_HINT } -CODE_ACTION_KINDS: Dict[CodeActionKind, SublimeKind] = { +CODE_ACTION_KINDS: dict[CodeActionKind, SublimeKind] = { CodeActionKind.QuickFix: KIND_QUICKFIX, CodeActionKind.Refactor: KIND_REFACTOR, CodeActionKind.Source: KIND_SOURCE } -DOCUMENT_HIGHLIGHT_KIND_NAMES: Dict[DocumentHighlightKind, str] = { +DOCUMENT_HIGHLIGHT_KIND_NAMES: dict[DocumentHighlightKind, str] = { DocumentHighlightKind.Text: "text", DocumentHighlightKind.Read: "read", DocumentHighlightKind.Write: "write" @@ -151,7 +151,7 @@ # Symbol scope to kind mapping, based on https://github.com/sublimetext-io/docs.sublimetext.io/issues/30 -SUBLIME_KIND_SCOPES: Dict[SublimeKind, str] = { +SUBLIME_KIND_SCOPES: dict[SublimeKind, str] = { sublime.KIND_KEYWORD: "keyword | storage.modifier | storage.type | keyword.declaration | variable.language | constant.language", # noqa: E501 sublime.KIND_TYPE: "entity.name.type | entity.name.class | entity.name.enum | entity.name.trait | entity.name.struct | entity.name.impl | entity.name.interface | entity.name.union | support.type | support.class", # noqa: E501 sublime.KIND_FUNCTION: "entity.name.function | entity.name.method | entity.name.macro | meta.method entity.name.function | support.function | meta.function-call variable.function | meta.function-call support.function | support.method | meta.method-call variable.function", # noqa: E501 @@ -161,7 +161,7 @@ sublime.KIND_VARIABLE: "entity.name.constant | constant.other | support.constant | variable.other | variable.parameter | variable.other.member | variable.other.readwrite.member" # noqa: E501 } -DOCUMENT_HIGHLIGHT_KIND_SCOPES: Dict[DocumentHighlightKind, str] = { +DOCUMENT_HIGHLIGHT_KIND_SCOPES: dict[DocumentHighlightKind, str] = { DocumentHighlightKind.Text: "region.bluish markup.highlight.text.lsp", DocumentHighlightKind.Read: "region.greenish markup.highlight.read.lsp", DocumentHighlightKind.Write: "region.yellowish markup.highlight.write.lsp" diff --git a/plugin/core/css.py b/plugin/core/css.py index 8f26b4677..0743dc794 100644 --- a/plugin/core/css.py +++ b/plugin/core/css.py @@ -1,5 +1,4 @@ from __future__ import annotations -from typing import Optional import sublime @@ -16,7 +15,7 @@ def __init__(self) -> None: self.annotations_classname = "lsp_annotation" -_css: Optional[CSS] = None +_css: CSS | None = None def load() -> None: diff --git a/plugin/core/diagnostics_storage.py b/plugin/core/diagnostics_storage.py index fd750204e..1e1ebcf17 100644 --- a/plugin/core/diagnostics_storage.py +++ b/plugin/core/diagnostics_storage.py @@ -3,7 +3,7 @@ from .url import parse_uri from .views import diagnostic_severity from collections import OrderedDict -from typing import Callable, Iterator, List, Tuple, TypeVar +from typing import Callable, Iterator, Tuple, TypeVar import functools ParsedUri = Tuple[str, str] @@ -22,7 +22,7 @@ class DiagnosticsStorage(OrderedDict): # # https://microsoft.github.io/language-server-protocol/specification#textDocument_publishDiagnostics - def add_diagnostics_async(self, document_uri: DocumentUri, diagnostics: List[Diagnostic]) -> None: + def add_diagnostics_async(self, document_uri: DocumentUri, diagnostics: list[Diagnostic]) -> None: """ Add `diagnostics` for `document_uri` to the store, replacing previously received `diagnoscis` for this `document_uri`. If `diagnostics` is the empty list, `document_uri` is removed from @@ -38,7 +38,7 @@ def add_diagnostics_async(self, document_uri: DocumentUri, diagnostics: List[Dia def filter_map_diagnostics_async( self, pred: Callable[[Diagnostic], bool], f: Callable[[ParsedUri, Diagnostic], T] - ) -> Iterator[Tuple[ParsedUri, List[T]]]: + ) -> Iterator[tuple[ParsedUri, list[T]]]: """ Yields `(uri, results)` items with `results` being a list of `f(diagnostic)` for each diagnostic for this `uri` with `pred(diagnostic) == True`, filtered by `bool(f(diagnostic))`. @@ -46,12 +46,12 @@ def filter_map_diagnostics_async( not more than once. Items and results are ordered as they came in from the server. """ for uri, diagnostics in self.items(): - results: List[T] = list(filter(None, map(functools.partial(f, uri), filter(pred, diagnostics)))) + results: list[T] = list(filter(None, map(functools.partial(f, uri), filter(pred, diagnostics)))) if results: yield uri, results def filter_map_diagnostics_flat_async(self, pred: Callable[[Diagnostic], bool], - f: Callable[[ParsedUri, Diagnostic], T]) -> Iterator[Tuple[ParsedUri, T]]: + f: Callable[[ParsedUri, Diagnostic], T]) -> Iterator[tuple[ParsedUri, T]]: """ Flattened variant of `filter_map_diagnostics_async()`. Yields `(uri, result)` items for each of the `result`s per `uri` instead. Each `uri` can be yielded more than once. Items are @@ -62,7 +62,7 @@ def filter_map_diagnostics_flat_async(self, pred: Callable[[Diagnostic], bool], for result in results: yield uri, result - def sum_total_errors_and_warnings_async(self) -> Tuple[int, int]: + def sum_total_errors_and_warnings_async(self) -> tuple[int, int]: """ Returns `(total_errors, total_warnings)` count of all diagnostics currently in store. """ @@ -71,21 +71,21 @@ def sum_total_errors_and_warnings_async(self) -> Tuple[int, int]: sum(map(severity_count(DiagnosticSeverity.Warning), self.values())), ) - def diagnostics_by_document_uri(self, document_uri: DocumentUri) -> List[Diagnostic]: + def diagnostics_by_document_uri(self, document_uri: DocumentUri) -> list[Diagnostic]: """ Returns possibly empty list of diagnostic for `document_uri`. """ return self.get(parse_uri(document_uri), []) - def diagnostics_by_parsed_uri(self, uri: ParsedUri) -> List[Diagnostic]: + def diagnostics_by_parsed_uri(self, uri: ParsedUri) -> list[Diagnostic]: """ Returns possibly empty list of diagnostic for `uri`. """ return self.get(uri, []) -def severity_count(severity: int) -> Callable[[List[Diagnostic]], int]: - def severity_count(diagnostics: List[Diagnostic]) -> int: +def severity_count(severity: int) -> Callable[[list[Diagnostic]], int]: + def severity_count(diagnostics: list[Diagnostic]) -> int: return len(list(filter(has_severity(severity), diagnostics))) return severity_count diff --git a/plugin/core/edit.py b/plugin/core/edit.py index eba72aeee..f7f4fa24f 100644 --- a/plugin/core/edit.py +++ b/plugin/core/edit.py @@ -33,16 +33,16 @@ def parse_workspace_edit(workspace_edit: WorkspaceEdit) -> WorkspaceChanges: return changes -def parse_range(range: Position) -> Tuple[int, int]: +def parse_range(range: Position) -> tuple[int, int]: return range['line'], min(UINT_MAX, range['character']) def apply_text_edits( view: sublime.View, - edits: Optional[List[TextEdit]], + edits: list[TextEdit] | None, *, - process_placeholders: Optional[bool] = False, - required_view_version: Optional[int] = None + process_placeholders: bool | None = False, + required_view_version: int | None = None ) -> None: if not edits: return diff --git a/plugin/core/file_watcher.py b/plugin/core/file_watcher.py index dded15571..722ef6983 100644 --- a/plugin/core/file_watcher.py +++ b/plugin/core/file_watcher.py @@ -3,7 +3,7 @@ from .protocol import WatchKind from abc import ABCMeta from abc import abstractmethod -from typing import List, Literal, Optional, Protocol, Tuple, Type, Union +from typing import Literal, Protocol, Tuple, Union DEFAULT_KIND = WatchKind.Create | WatchKind.Change | WatchKind.Delete @@ -12,8 +12,8 @@ FileWatcherEvent = Tuple[FileWatcherEventType, FilePath] -def lsp_watch_kind_to_file_watcher_event_types(kind: WatchKind) -> List[FileWatcherEventType]: - event_types: List[FileWatcherEventType] = [] +def lsp_watch_kind_to_file_watcher_event_types(kind: WatchKind) -> list[FileWatcherEventType]: + event_types: list[FileWatcherEventType] = [] if kind & WatchKind.Create: event_types.append('create') if kind & WatchKind.Change: @@ -32,7 +32,7 @@ def file_watcher_event_type_to_lsp_file_change_type(kind: FileWatcherEventType) class FileWatcherProtocol(Protocol): - def on_file_event_async(self, events: List[FileWatcherEvent]) -> None: + def on_file_event_async(self, events: list[FileWatcherEvent]) -> None: """ Called on file watcher events. This API must be triggered on async thread. @@ -55,9 +55,9 @@ class FileWatcher(metaclass=ABCMeta): def create( cls, root_path: str, - patterns: List[str], - events: List[FileWatcherEventType], - ignores: List[str], + patterns: list[str], + events: list[FileWatcherEventType], + ignores: list[str], handler: FileWatcherProtocol ) -> FileWatcher: """ @@ -79,15 +79,15 @@ def destroy(self) -> None: pass -watcher_implementation: Optional[Type[FileWatcher]] = None +watcher_implementation: type[FileWatcher] | None = None -def register_file_watcher_implementation(file_watcher: Type[FileWatcher]) -> None: +def register_file_watcher_implementation(file_watcher: type[FileWatcher]) -> None: global watcher_implementation if watcher_implementation: print('LSP: Watcher implementation already registered. Overwriting.') watcher_implementation = file_watcher -def get_file_watcher_implementation() -> Optional[Type[FileWatcher]]: +def get_file_watcher_implementation() -> type[FileWatcher] | None: return watcher_implementation diff --git a/plugin/core/input_handlers.py b/plugin/core/input_handlers.py index 4aea200c1..54b2f4285 100644 --- a/plugin/core/input_handlers.py +++ b/plugin/core/input_handlers.py @@ -2,7 +2,7 @@ from .constants import ST_VERSION from abc import ABCMeta from abc import abstractmethod -from typing import Any, Callable, Dict, List, Optional, Tuple, Union +from typing import Any, Callable, List, Tuple, Union from typing import final from typing_extensions import ParamSpec import functools @@ -60,7 +60,7 @@ class PreselectedListInputHandler(sublime_plugin.ListInputHandler, metaclass=ABC """ def __init__( - self, window: sublime.Window, initial_value: Optional[Union[str, sublime.ListInputItem]] = None + self, window: sublime.Window, initial_value: str | sublime.ListInputItem | None = None ) -> None: super().__init__() self._window = window @@ -103,13 +103,13 @@ def input(self, args): names are not used in another way in the command's class. """ - def __init__(self, command: sublime_plugin.WindowCommand, args: Dict[str, Any]) -> None: + def __init__(self, command: sublime_plugin.WindowCommand, args: dict[str, Any]) -> None: super().__init__() self.command = command self.args = args self.text = getattr(command, '_text', '') - self.listener: Optional[sublime_plugin.TextChangeListener] = None - self.input_view: Optional[sublime.View] = None + self.listener: sublime_plugin.TextChangeListener | None = None + self.input_view: sublime.View | None = None def attach_listener(self) -> None: for buffer in sublime._buffers(): # type: ignore @@ -130,7 +130,7 @@ def attach_listener(self) -> None: selection.add(len(self.text)) @final - def list_items(self) -> List[sublime.ListInputItem]: + def list_items(self) -> list[sublime.ListInputItem]: if not self.text: # Show initial items when the command was just invoked return self.get_list_items() or [sublime.ListInputItem("No Results", "")] else: # Items were updated after typing @@ -142,7 +142,7 @@ def list_items(self) -> List[sublime.ListInputItem]: # Trick to select the topmost item; see https://github.com/sublimehq/sublime_text/issues/6162 sublime.set_timeout(self._select_first_row) return [sublime.ListInputItem("", "")] + items - return [sublime.ListInputItem('No Symbol found: "{}"'.format(self.text), "")] + return [sublime.ListInputItem(f'No Symbol found: "{self.text}"', "")] def _select_first_row(self) -> None: self.command.window.run_command('move', {'by': 'lines', 'forward': True}) @@ -152,7 +152,7 @@ def initial_text(self) -> str: sublime.set_timeout(self.attach_listener) return self.text - def initial_selection(self) -> List[Tuple[int, int]]: + def initial_selection(self) -> list[tuple[int, int]]: pt = len(self.text) return [(pt, pt)] @@ -171,11 +171,11 @@ def on_modified(self, text: str) -> None: """ Called after changes have been made to the input, with the text of the input field passed as argument. """ pass - def get_list_items(self) -> List[sublime.ListInputItem]: + def get_list_items(self) -> list[sublime.ListInputItem]: """ The list items which are initially shown. """ return [] - def update(self, items: List[sublime.ListInputItem]) -> None: + def update(self, items: list[sublime.ListInputItem]) -> None: """ Call this method to update the list items. """ if not self.input_view: return @@ -202,7 +202,7 @@ def is_applicable(cls, buffer: sublime.Buffer) -> bool: return False @debounced - def on_text_changed(self, changes: List[sublime.TextChange]) -> None: + def on_text_changed(self, changes: list[sublime.TextChange]) -> None: handler = self.weakhandler() if not handler: return diff --git a/plugin/core/logging.py b/plugin/core/logging.py index 06129acf3..ebaeb3629 100644 --- a/plugin/core/logging.py +++ b/plugin/core/logging.py @@ -27,7 +27,7 @@ def trace() -> None: previous_frame = current_frame.f_back file_name, line_number, function_name, _, _ = inspect.getframeinfo(previous_frame) # type: ignore file_name = file_name[len(sublime.packages_path()) + len("/LSP/"):] - debug("TRACE {0:<32} {1}:{2}".format(function_name, file_name, line_number)) + debug(f"TRACE {function_name:<32} {file_name}:{line_number}") def exception_log(message: str, ex: Exception) -> None: diff --git a/plugin/core/message_request_handler.py b/plugin/core/message_request_handler.py index ee382211a..7ae460389 100644 --- a/plugin/core/message_request_handler.py +++ b/plugin/core/message_request_handler.py @@ -5,11 +5,11 @@ from .sessions import Session from .views import show_lsp_popup from .views import text2html -from typing import Any, Dict, List +from typing import Any import sublime -ICONS: Dict[MessageType, str] = { +ICONS: dict[MessageType, str] = { MessageType.Error: '❗', MessageType.Warning: '⚠️', MessageType.Info: 'ℹ️', @@ -17,7 +17,7 @@ } -class MessageRequestHandler(): +class MessageRequestHandler: def __init__( self, view: sublime.View, session: Session, request_id: Any, params: ShowMessageRequestParams, source: str ) -> None: @@ -32,14 +32,14 @@ def __init__( self.source = source def show(self) -> None: - formatted: List[str] = [] - formatted.append("

{}

".format(self.source)) + formatted: list[str] = [] + formatted.append(f"

{self.source}

") icon = ICONS.get(self.message_type, '') - formatted.append("
{} {}
".format(icon, text2html(self.message))) + formatted.append(f"
{icon} {text2html(self.message)}
") if self.action_titles: - buttons: List[str] = [] + buttons: list[str] = [] for idx, title in enumerate(self.action_titles): - buttons.append("{}".format(idx, text2html(title))) + buttons.append(f"{text2html(title)}") formatted.append("
" + " ".join(buttons) + "
") show_lsp_popup( self.view, diff --git a/plugin/core/open.py b/plugin/core/open.py index f4d944498..43a364a76 100644 --- a/plugin/core/open.py +++ b/plugin/core/open.py @@ -8,7 +8,6 @@ from .protocol import UINT_MAX from .url import parse_uri from .views import range_to_region -from typing import Dict, Optional, Tuple from typing import cast from urllib.parse import unquote, urlparse import os @@ -19,17 +18,17 @@ import webbrowser -opening_files: Dict[str, Tuple[Promise[Optional[sublime.View]], ResolveFunc[Optional[sublime.View]]]] = {} +opening_files: dict[str, tuple[Promise[sublime.View | None], ResolveFunc[sublime.View | None]]] = {} FRAGMENT_PATTERN = re.compile(r'^L?(\d+)(?:,(\d+))?(?:-L?(\d+)(?:,(\d+))?)?') -def lsp_range_from_uri_fragment(fragment: str) -> Optional[Range]: +def lsp_range_from_uri_fragment(fragment: str) -> Range | None: match = FRAGMENT_PATTERN.match(fragment) if match: selection: Range = {'start': {'line': 0, 'character': 0}, 'end': {'line': 0, 'character': 0}} # Line and column numbers in the fragment are assumed to be 1-based and need to be converted to 0-based # numbers for the LSP Position structure. - start_line, start_column, end_line, end_column = [max(0, int(g) - 1) if g else None for g in match.groups()] + start_line, start_column, end_line, end_column = (max(0, int(g) - 1) if g else None for g in match.groups()) if start_line: selection['start']['line'] = start_line selection['end']['line'] = start_line @@ -47,7 +46,7 @@ def lsp_range_from_uri_fragment(fragment: str) -> Optional[Range]: def open_file_uri( window: sublime.Window, uri: DocumentUri, flags: int = 0, group: int = -1 -) -> Promise[Optional[sublime.View]]: +) -> Promise[sublime.View | None]: decoded_uri = unquote(uri) # decode percent-encoded characters parsed = urlparse(decoded_uri) @@ -59,7 +58,7 @@ def open_file_uri( return open_promise -def _select_and_center(view: Optional[sublime.View], r: Range) -> Optional[sublime.View]: +def _select_and_center(view: sublime.View | None, r: Range) -> sublime.View | None: if view: return center_selection(view, r) return None @@ -75,7 +74,7 @@ def _return_existing_view(flags: int, existing_view_group: int, active_group: in return not bool(flags & sublime.FORCE_GROUP) -def _find_open_file(window: sublime.Window, fname: str, group: int = -1) -> Optional[sublime.View]: +def _find_open_file(window: sublime.Window, fname: str, group: int = -1) -> sublime.View | None: """A replacement for Window.find_open_file that prefers the active view instead of the leftmost one.""" _group = window.active_group() if group == -1 else group view = window.active_view_in_group(_group) @@ -86,7 +85,7 @@ def _find_open_file(window: sublime.Window, fname: str, group: int = -1) -> Opti def open_file( window: sublime.Window, uri: DocumentUri, flags: int = 0, group: int = -1 -) -> Promise[Optional[sublime.View]]: +) -> Promise[sublime.View | None]: """ Open a file asynchronously. It is only safe to call this function from the UI thread. @@ -116,7 +115,7 @@ def open_file( return value[0] # Prepare a new promise to be resolved by a future on_load event (see the event listener in main.py) - def fullfill(resolve: ResolveFunc[Optional[sublime.View]]) -> None: + def fullfill(resolve: ResolveFunc[sublime.View | None]) -> None: global opening_files # Save the promise in the first element of the tuple -- except we cannot yet do that here opening_files[file] = (None, resolve) # type: ignore @@ -164,5 +163,5 @@ def open_externally(uri: str, take_focus: bool) -> bool: subprocess.check_call(("xdg-open", uri)) return True except Exception as ex: - exception_log("Failed to open {}".format(uri), ex) + exception_log(f"Failed to open {uri}", ex) return False diff --git a/plugin/core/panels.py b/plugin/core/panels.py index 0fc0d3dad..3e74e5509 100644 --- a/plugin/core/panels.py +++ b/plugin/core/panels.py @@ -1,7 +1,7 @@ from __future__ import annotations from .types import PANEL_FILE_REGEX from .types import PANEL_LINE_REGEX -from typing import Iterable, Optional +from typing import Iterable import sublime @@ -39,7 +39,7 @@ class PanelName: class PanelManager: def __init__(self, window: sublime.Window) -> None: self._window = window - self._rename_panel_buttons: Optional[sublime.PhantomSet] = None + self._rename_panel_buttons: sublime.PhantomSet | None = None def destroy_output_panels(self) -> None: for field in filter(lambda a: not a.startswith('__'), PanelName.__dict__.keys()): @@ -51,12 +51,12 @@ def destroy_output_panels(self) -> None: self._rename_panel_buttons = None def toggle_output_panel(self, panel_type: str) -> None: - panel_name = "output.{}".format(panel_type) + panel_name = f"output.{panel_type}" command = "hide_panel" if self.is_panel_open(panel_type) else "show_panel" self._window.run_command(command, {"panel": panel_name}) def is_panel_open(self, panel_name: str) -> bool: - return self._window.is_valid() and self._window.active_panel() == "output.{}".format(panel_name) + return self._window.is_valid() and self._window.active_panel() == f"output.{panel_name}" def update_log_panel(self, scroll_to_selection: bool = False) -> None: panel = self.ensure_log_panel() @@ -66,31 +66,31 @@ def update_log_panel(self, scroll_to_selection: bool = False) -> None: panel.show(panel.sel(), animate=False) def ensure_panel(self, name: str, result_file_regex: str, result_line_regex: str, - syntax: str, context_menu: Optional[str] = None) -> Optional[sublime.View]: + syntax: str, context_menu: str | None = None) -> sublime.View | None: return self.get_panel(name) or \ self._create_panel(name, result_file_regex, result_line_regex, syntax, context_menu) - def ensure_diagnostics_panel(self) -> Optional[sublime.View]: + def ensure_diagnostics_panel(self) -> sublime.View | None: return self.ensure_panel("diagnostics", PANEL_FILE_REGEX, PANEL_LINE_REGEX, "Packages/LSP/Syntaxes/Diagnostics.sublime-syntax") - def ensure_log_panel(self) -> Optional[sublime.View]: + def ensure_log_panel(self) -> sublime.View | None: return self.ensure_panel(PanelName.Log, "", "", "Packages/LSP/Syntaxes/ServerLog.sublime-syntax", "Context LSP Log Panel.sublime-menu") - def ensure_references_panel(self) -> Optional[sublime.View]: + def ensure_references_panel(self) -> sublime.View | None: return self.ensure_panel("references", PANEL_FILE_REGEX, PANEL_LINE_REGEX, "Packages/LSP/Syntaxes/References.sublime-syntax") - def ensure_rename_panel(self) -> Optional[sublime.View]: + def ensure_rename_panel(self) -> sublime.View | None: return self.ensure_panel(PanelName.Rename, PANEL_FILE_REGEX, PANEL_LINE_REGEX, "Packages/LSP/Syntaxes/References.sublime-syntax") - def get_panel(self, panel_name: str) -> Optional[sublime.View]: + def get_panel(self, panel_name: str) -> sublime.View | None: return self._window.find_output_panel(panel_name) def _create_panel(self, name: str, result_file_regex: str, result_line_regex: str, - syntax: str, context_menu: Optional[str] = None) -> Optional[sublime.View]: + syntax: str, context_menu: str | None = None) -> sublime.View | None: panel = self.create_output_panel(name) if not panel: return None @@ -112,7 +112,7 @@ def _create_panel(self, name: str, result_file_regex: str, result_line_regex: st panel.set_read_only(True) return panel - def create_output_panel(self, name: str) -> Optional[sublime.View]: + def create_output_panel(self, name: str) -> sublime.View | None: panel = self._window.create_output_panel(name) settings = panel.settings() for key, value in OUTPUT_PANEL_SETTINGS.items(): diff --git a/plugin/core/paths.py b/plugin/core/paths.py index a600f49c8..47346d9a5 100644 --- a/plugin/core/paths.py +++ b/plugin/core/paths.py @@ -3,10 +3,10 @@ from .sessions import Session from .views import parse_uri from pathlib import Path -from typing import Iterable, Optional, Tuple +from typing import Iterable -def simple_path(session: Optional[Session], uri: DocumentUri) -> str: +def simple_path(session: Session | None, uri: DocumentUri) -> str: scheme, path = parse_uri(uri) if not session or scheme != 'file': return path @@ -14,7 +14,7 @@ def simple_path(session: Optional[Session], uri: DocumentUri) -> str: return str(simple_path) if simple_path else path -def project_path(project_folders: Iterable[Path], file_path: Path) -> Optional[Path]: +def project_path(project_folders: Iterable[Path], file_path: Path) -> Path | None: """ The project path of `/path/to/project/file` in the project `/path/to/project` is `file`. """ @@ -25,7 +25,7 @@ def project_path(project_folders: Iterable[Path], file_path: Path) -> Optional[P return file -def simple_project_path(project_folders: Iterable[Path], file_path: Path) -> Optional[Path]: +def simple_project_path(project_folders: Iterable[Path], file_path: Path) -> Path | None: """ The simple project path of `/path/to/project/file` in the project `/path/to/project` is `project/file`. """ @@ -36,7 +36,7 @@ def simple_project_path(project_folders: Iterable[Path], file_path: Path) -> Opt return folder.name / file -def resolve_simple_project_path(project_folders: Iterable[Path], file_path: Path) -> Optional[Path]: +def resolve_simple_project_path(project_folders: Iterable[Path], file_path: Path) -> Path | None: """ The inverse of `simple_project_path()`. """ @@ -48,7 +48,7 @@ def resolve_simple_project_path(project_folders: Iterable[Path], file_path: Path return None -def project_base_dir(project_folders: Iterable[Path], file_path: Path) -> Optional[Path]: +def project_base_dir(project_folders: Iterable[Path], file_path: Path) -> Path | None: """ The project base dir of `/path/to/project/file` in the project `/path/to/project` is `/path/to`. """ @@ -59,7 +59,7 @@ def project_base_dir(project_folders: Iterable[Path], file_path: Path) -> Option return folder.parent -def split_project_path(project_folders: Iterable[Path], file_path: Path) -> Optional[Tuple[Path, Path]]: +def split_project_path(project_folders: Iterable[Path], file_path: Path) -> tuple[Path, Path] | None: abs_path = file_path.resolve() for folder in project_folders: try: diff --git a/plugin/core/progress.py b/plugin/core/progress.py index f1950bec1..2b9127413 100644 --- a/plugin/core/progress.py +++ b/plugin/core/progress.py @@ -1,5 +1,4 @@ from __future__ import annotations -from typing import Optional, Union import sublime @@ -7,8 +6,8 @@ class ProgressReporter: def __init__(self, title: str) -> None: self.title = title - self._message: Optional[str] = None - self._percentage: Union[None, int, float] = None + self._message: str | None = None + self._percentage: None | int | float = None def __del__(self) -> None: pass @@ -22,7 +21,7 @@ def _render(self) -> str: result += fmt.format(self._percentage) return result - def __call__(self, message: Optional[str], percentage: Union[None, int, float]) -> None: + def __call__(self, message: str | None, percentage: None | int | float) -> None: if percentage is not None: self._percentage = percentage if message is not None: @@ -31,8 +30,8 @@ def __call__(self, message: Optional[str], percentage: Union[None, int, float]) class ViewProgressReporter(ProgressReporter): - def __init__(self, view: sublime.View, key: str, title: str, message: Optional[str] = None, - percentage: Union[None, int, float] = None) -> None: + def __init__(self, view: sublime.View, key: str, title: str, message: str | None = None, + percentage: None | int | float = None) -> None: super().__init__(title) self._view = view self._key = key @@ -42,15 +41,15 @@ def __del__(self) -> None: self._view.erase_status(self._key) super().__del__() - def __call__(self, message: Optional[str] = None, percentage: Union[None, int, float] = None) -> None: + def __call__(self, message: str | None = None, percentage: None | int | float = None) -> None: super().__call__(message, percentage) self._view.set_status(self._key, self._render()) class WindowProgressReporter(ProgressReporter): - def __init__(self, window: sublime.Window, key: str, title: str, message: Optional[str] = None, - percentage: Union[None, int, float] = None) -> None: + def __init__(self, window: sublime.Window, key: str, title: str, message: str | None = None, + percentage: None | int | float = None) -> None: super().__init__(title) self._window = window self._key = key @@ -61,7 +60,7 @@ def __del__(self) -> None: view.erase_status(self._key) super().__del__() - def __call__(self, message: Optional[str] = None, percentage: Union[None, int, float] = None) -> None: + def __call__(self, message: str | None = None, percentage: None | int | float = None) -> None: super().__call__(message, percentage) display = self._render() for view in self._window.views(): @@ -70,8 +69,8 @@ def __call__(self, message: Optional[str] = None, percentage: Union[None, int, f class ApplicationProgressReporter(ProgressReporter): - def __init__(self, key: str, title: str, message: Optional[str] = None, - percentage: Union[None, int, float] = None) -> None: + def __init__(self, key: str, title: str, message: str | None = None, + percentage: None | int | float = None) -> None: super().__init__(title) self._key = key self.__call__(message, percentage) @@ -82,7 +81,7 @@ def __del__(self) -> None: view.erase_status(self._key) super().__del__() - def __call__(self, message: Optional[str] = None, percentage: Union[None, int, float] = None) -> None: + def __call__(self, message: str | None = None, percentage: None | int | float = None) -> None: super().__call__(message, percentage) display = self._render() for window in sublime.windows(): diff --git a/plugin/core/promise.py b/plugin/core/promise.py index 1d984d02d..b47f018d0 100644 --- a/plugin/core/promise.py +++ b/plugin/core/promise.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Callable, Generic, List, Optional, Protocol, Tuple, TypeVar, Union +from typing import Callable, Generic, Protocol, Tuple, TypeVar, Union import functools import threading @@ -88,7 +88,7 @@ class Executor(Generic[TExecutor]): __slots__ = ("resolver",) def __init__(self) -> None: - self.resolver: Optional[ResolveFunc[TExecutor]] = None + self.resolver: ResolveFunc[TExecutor] | None = None def __call__(self, resolver: ResolveFunc[TExecutor]) -> None: self.resolver = resolver @@ -100,7 +100,7 @@ def __call__(self, resolver: ResolveFunc[TExecutor]) -> None: # Could also support passing plain S. @staticmethod - def all(promises: List[Promise[S]]) -> Promise[List[S]]: + def all(promises: list[Promise[S]]) -> Promise[list[S]]: """ Takes a list of promises and returns a Promise that gets resolved when all promises gets resolved. @@ -110,7 +110,7 @@ def all(promises: List[Promise[S]]) -> Promise[List[S]]: :returns: A promise that gets resolved when all passed promises gets resolved. Gets passed a list with all resolved values. """ - def executor(resolve: ResolveFunc[List[S]]) -> None: + def executor(resolve: ResolveFunc[list[S]]) -> None: was_resolved = False def recheck_resolve_status(_: S) -> None: @@ -140,12 +140,12 @@ def __init__(self, executor_func: ExecutorFunc[T]) -> None: """ self.resolved = False self.mutex = threading.Lock() - self.callbacks: List[ResolveFunc[T]] = [] + self.callbacks: list[ResolveFunc[T]] = [] executor_func(lambda resolve_value=None: self._do_resolve(resolve_value)) def __repr__(self) -> str: if self.resolved: - return 'Promise({})'.format(self.value) + return f'Promise({self.value})' return 'Promise()' def then(self, onfullfilled: FullfillFunc[T, TResult]) -> Promise[TResult]: diff --git a/plugin/core/protocol.py b/plugin/core/protocol.py index 85a7ae385..9cf429aec 100644 --- a/plugin/core/protocol.py +++ b/plugin/core/protocol.py @@ -2,7 +2,7 @@ from .typing import StrEnum from enum import IntEnum, IntFlag from functools import total_ordering -from typing import Any, Dict, Generic, Iterable, List, Literal, Mapping, Optional, TypedDict, TypeVar, Union +from typing import Any, Dict, Generic, Iterable, List, Literal, Mapping, TypedDict, TypeVar, Union from typing_extensions import NotRequired import sublime @@ -6026,14 +6026,14 @@ def __init__( self, method: str, params: Any = None, - view: Optional[sublime.View] = None, + view: sublime.View | None = None, progress: bool = False, partial_results: bool = False ) -> None: self.method = method self.params = params self.view = view - self.progress: Union[bool, str] = progress + self.progress: bool | str = progress self.partial_results = partial_results @classmethod @@ -6091,7 +6091,7 @@ def semanticTokensRange(cls, params: SemanticTokensRangeParams, view: sublime.Vi @classmethod def prepareCallHierarchy( cls, params: CallHierarchyPrepareParams, view: sublime.View - ) -> Request[Union[List[CallHierarchyItem], Error, None]]: + ) -> Request[list[CallHierarchyItem] | Error | None]: return Request("textDocument/prepareCallHierarchy", params, view, progress=True) @classmethod @@ -6169,7 +6169,7 @@ def shutdown(cls) -> Request: def __repr__(self) -> str: return self.method + " " + str(self.params) - def to_payload(self, id: int) -> Dict[str, Any]: + def to_payload(self, id: int) -> dict[str, Any]: payload = { "jsonrpc": "2.0", "id": id, @@ -6191,14 +6191,14 @@ def __init__(self, code: int, message: str, data: Any = None) -> None: def from_lsp(cls, params: Any) -> Error: return Error(params["code"], params["message"], params.get("data")) - def to_lsp(self) -> Dict[str, Any]: + def to_lsp(self) -> dict[str, Any]: result = {"code": self.code, "message": super().__str__()} if self.data: result["data"] = self.data return result def __str__(self) -> str: - return "{} ({})".format(super().__str__(), self.code) + return f"{super().__str__()} ({self.code})" @classmethod def from_exception(cls, ex: Exception) -> Error: @@ -6216,7 +6216,7 @@ def __init__(self, request_id: Any, result: T) -> None: self.request_id = request_id self.result = result - def to_payload(self) -> Dict[str, Any]: + def to_payload(self) -> dict[str, Any]: return { "id": self.request_id, "jsonrpc": "2.0", @@ -6275,7 +6275,7 @@ def exit(cls) -> Notification: def __repr__(self) -> str: return self.method + " " + str(self.params) - def to_payload(self) -> Dict[str, Any]: + def to_payload(self) -> dict[str, Any]: payload = { "jsonrpc": "2.0", "method": self.method, @@ -6286,13 +6286,13 @@ def to_payload(self) -> Dict[str, Any]: @total_ordering -class Point(object): +class Point: def __init__(self, row: int, col: int) -> None: self.row = int(row) self.col = int(col) # in UTF-16 def __repr__(self) -> str: - return "{}:{}".format(self.row, self.col) + return f"{self.row}:{self.col}" def __eq__(self, other: object) -> bool: if not isinstance(other, Point): diff --git a/plugin/core/registry.py b/plugin/core/registry.py index 1c1284062..ea9deab2f 100644 --- a/plugin/core/registry.py +++ b/plugin/core/registry.py @@ -12,7 +12,7 @@ from .windows import WindowManager from .windows import WindowRegistry from functools import partial -from typing import Any, Generator, Iterable, List, Optional, Union +from typing import Any, Generator, Iterable import operator import sublime import sublime_plugin @@ -21,7 +21,7 @@ windows = WindowRegistry() -def best_session(view: sublime.View, sessions: Iterable[Session], point: Optional[int] = None) -> Optional[Session]: +def best_session(view: sublime.View, sessions: Iterable[Session], point: int | None = None) -> Session | None: if point is None: try: point = view.sel()[0].b @@ -33,7 +33,7 @@ def best_session(view: sublime.View, sessions: Iterable[Session], point: Optiona return None -def get_position(view: sublime.View, event: Optional[dict] = None, point: Optional[int] = None) -> Optional[int]: +def get_position(view: sublime.View, event: dict | None = None, point: int | None = None) -> int | None: if isinstance(point, int): return point if event: @@ -63,7 +63,7 @@ class LspWindowCommand(sublime_plugin.WindowCommand): def is_enabled(self) -> bool: return self.session() is not None - def session(self) -> Optional[Session]: + def session(self) -> Session | None: wm = windows.lookup(self.window) if not wm: return None @@ -87,7 +87,7 @@ def sessions(self) -> Generator[Session, None, None]: continue yield session - def session_by_name(self, session_name: str) -> Optional[Session]: + def session_by_name(self, session_name: str) -> Session | None: wm = windows.lookup(self.window) if not wm: return None @@ -114,7 +114,7 @@ class LspTextCommand(sublime_plugin.TextCommand): # name attached to the active view. session_name = '' - def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None) -> bool: + def is_enabled(self, event: dict | None = None, point: int | None = None) -> bool: if self.capability: # At least one active session with the given capability must exist. position = get_position(self.view, event, point) @@ -135,17 +135,17 @@ def want_event(self) -> bool: return True @staticmethod - def applies_to_context_menu(event: Optional[dict]) -> bool: + def applies_to_context_menu(event: dict | None) -> bool: return event is not None and 'x' in event - def get_listener(self) -> Optional[AbstractViewListener]: + def get_listener(self) -> AbstractViewListener | None: return windows.listener_for_view(self.view) - def best_session(self, capability: str, point: Optional[int] = None) -> Optional[Session]: + def best_session(self, capability: str, point: int | None = None) -> Session | None: listener = self.get_listener() return listener.session_async(capability, point) if listener else None - def session_by_name(self, name: Optional[str] = None, capability_path: Optional[str] = None) -> Optional[Session]: + def session_by_name(self, name: str | None = None, capability_path: str | None = None) -> Session | None: target = name if name else self.session_name listener = self.get_listener() if listener: @@ -157,7 +157,7 @@ def session_by_name(self, name: Optional[str] = None, capability_path: Optional[ return None return None - def sessions(self, capability_path: Optional[str] = None) -> Generator[Session, None, None]: + def sessions(self, capability_path: str | None = None) -> Generator[Session, None, None]: listener = self.get_listener() if listener: for sv in listener.session_views_async(): @@ -172,11 +172,11 @@ class LspOpenLocationCommand(LspWindowCommand): def run( self, - location: Union[Location, LocationLink], - session_name: Optional[str] = None, + location: Location | LocationLink, + session_name: str | None = None, flags: int = 0, group: int = -1, - event: Optional[dict] = None + event: dict | None = None ) -> None: if event: modifier_keys = event.get('modifier_keys') @@ -191,23 +191,23 @@ def want_event(self) -> bool: return True def _run_async( - self, location: Union[Location, LocationLink], session_name: Optional[str], flags: int, group: int + self, location: Location | LocationLink, session_name: str | None, flags: int, group: int ) -> None: session = self.session_by_name(session_name) if session_name else self.session() if session: session.open_location_async(location, flags, group) \ .then(lambda view: self._handle_continuation(location, view is not None)) - def _handle_continuation(self, location: Union[Location, LocationLink], success: bool) -> None: + def _handle_continuation(self, location: Location | LocationLink, success: bool) -> None: if not success: uri, _ = get_uri_and_position_from_location(location) - message = "Failed to open {}".format(uri) + message = f"Failed to open {uri}" sublime.status_message(message) class LspRestartServerCommand(LspTextCommand): - def run(self, edit: Any, config_name: Optional[str] = None) -> None: + def run(self, edit: Any, config_name: str | None = None) -> None: wm = windows.lookup(self.view.window()) if not wm: return @@ -231,7 +231,7 @@ def run_async() -> None: sublime.set_timeout_async(run_async) -def navigate_diagnostics(view: sublime.View, point: Optional[int], forward: bool = True) -> None: +def navigate_diagnostics(view: sublime.View, point: int | None, forward: bool = True) -> None: try: uri = uri_from_view(view) except MissingUriError: @@ -239,7 +239,7 @@ def navigate_diagnostics(view: sublime.View, point: Optional[int], forward: bool wm = windows.lookup(view.window()) if not wm: return - diagnostics: List[Diagnostic] = [] + diagnostics: list[Diagnostic] = [] for session in wm.get_sessions(): diagnostics.extend(session.diagnostics.diagnostics_by_document_uri(uri)) if not diagnostics: @@ -267,11 +267,11 @@ def navigate_diagnostics(view: sublime.View, point: Optional[int], forward: bool class LspNextDiagnosticCommand(LspTextCommand): - def run(self, edit: sublime.Edit, point: Optional[int] = None) -> None: + def run(self, edit: sublime.Edit, point: int | None = None) -> None: navigate_diagnostics(self.view, point, forward=True) class LspPrevDiagnosticCommand(LspTextCommand): - def run(self, edit: sublime.Edit, point: Optional[int] = None) -> None: + def run(self, edit: sublime.Edit, point: int | None = None) -> None: navigate_diagnostics(self.view, point, forward=False) diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index fb822248e..bff4245d2 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -112,7 +112,7 @@ from abc import abstractmethod from abc import abstractproperty from enum import IntEnum -from typing import Any, Callable, Dict, Generator, List, Optional, Protocol, Set, Tuple, Type, TypeVar, Union +from typing import Any, Callable, Generator, List, Protocol, TypeVar from typing import cast from typing_extensions import TypeAlias, TypeGuard from weakref import WeakSet @@ -136,7 +136,7 @@ def is_diagnostic_server_cancellation_data(data: Any) -> TypeGuard[DiagnosticSer return isinstance(data, dict) and 'retriggerRequest' in data -def get_semantic_tokens_map(custom_tokens_map: Optional[Dict[str, str]]) -> Tuple[Tuple[str, str], ...]: +def get_semantic_tokens_map(custom_tokens_map: dict[str, str] | None) -> tuple[tuple[str, str], ...]: tokens_scope_map = SEMANTIC_TOKENS_MAP.copy() if custom_tokens_map is not None: tokens_scope_map.update(custom_tokens_map) @@ -145,12 +145,12 @@ def get_semantic_tokens_map(custom_tokens_map: Optional[Dict[str, str]]) -> Tupl @functools.lru_cache(maxsize=128) def decode_semantic_token( - types_legend: Tuple[str, ...], - modifiers_legend: Tuple[str, ...], - tokens_scope_map: Tuple[Tuple[str, str], ...], + types_legend: tuple[str, ...], + modifiers_legend: tuple[str, ...], + tokens_scope_map: tuple[tuple[str, str], ...], token_type_encoded: int, token_modifiers_encoded: int -) -> Tuple[str, List[str], Optional[str]]: +) -> tuple[str, list[str], str | None]: """ This function converts the token type and token modifiers from encoded numbers into names, based on the legend from the server. It also returns the corresponding scope name, which will be used for the highlighting color, either @@ -167,7 +167,7 @@ def decode_semantic_token( if token_type in tokens_scope_map_dict: for token_modifier in token_modifiers: # this approach is limited to consider at most one modifier for the scope lookup - key = "{}.{}".format(token_type, token_modifier) + key = f"{token_type}.{token_modifier}" if key in tokens_scope_map_dict: scope = tokens_scope_map_dict[key] + " meta.semantic-token.{}.{}.lsp".format( token_type.lower(), token_modifier.lower()) @@ -175,9 +175,9 @@ def decode_semantic_token( else: scope = tokens_scope_map_dict[token_type] if token_modifiers: - scope += " meta.semantic-token.{}.{}.lsp".format(token_type.lower(), token_modifiers[0].lower()) + scope += f" meta.semantic-token.{token_type.lower()}.{token_modifiers[0].lower()}.lsp" else: - scope += " meta.semantic-token.{}.lsp".format(token_type.lower()) + scope += f" meta.semantic-token.{token_type.lower()}.lsp" return token_type, token_modifiers, scope @@ -196,21 +196,21 @@ def window(self) -> sublime.Window: raise NotImplementedError() @abstractmethod - def sessions(self, view: sublime.View, capability: Optional[str] = None) -> Generator[Session, None, None]: + def sessions(self, view: sublime.View, capability: str | None = None) -> Generator[Session, None, None]: """ Iterate over the sessions stored in this manager, applicable to the given view, with the given capability. """ raise NotImplementedError() @abstractmethod - def get_project_path(self, file_path: str) -> Optional[str]: + def get_project_path(self, file_path: str) -> str | None: """ Get the project path for the given file. """ raise NotImplementedError() @abstractmethod - def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> Optional[str]: + def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> str | None: """ Should the diagnostics for this URI be shown in the view? Return a reason why not """ @@ -235,22 +235,22 @@ def on_diagnostics_updated(self) -> None: # Event callbacks @abstractmethod - def on_post_exit_async(self, session: Session, exit_code: int, exception: Optional[Exception]) -> None: + def on_post_exit_async(self, session: Session, exit_code: int, exception: Exception | None) -> None: """ The given Session has stopped with the given exit code. """ raise NotImplementedError() -def _enum_to_list(e: Type[IntEnum]) -> List[int]: +def _enum_to_list(e: type[IntEnum]) -> list[int]: return [v.value for v in e] -def _enum_like_class_to_list(c: Type[object]) -> List[Union[int, str]]: +def _enum_like_class_to_list(c: type[object]) -> list[int | str]: return [v for k, v in c.__dict__.items() if not k.startswith('_')] -def get_initialize_params(variables: Dict[str, str], workspace_folders: List[WorkspaceFolder], +def get_initialize_params(variables: dict[str, str], workspace_folders: list[WorkspaceFolder], config: ClientConfig) -> InitializeParams: completion_kinds = cast(List[CompletionItemKind], _enum_to_list(CompletionItemKind)) symbol_kinds = cast(List[SymbolKind], _enum_to_list(SymbolKind)) @@ -549,19 +549,19 @@ def listener(self) -> weakref.ref[AbstractViewListener]: def session_buffer(self) -> SessionBufferProtocol: ... - def get_uri(self) -> Optional[DocumentUri]: + def get_uri(self) -> DocumentUri | None: ... - def get_language_id(self) -> Optional[str]: + def get_language_id(self) -> str | None: ... - def get_view_for_group(self, group: int) -> Optional[sublime.View]: + def get_view_for_group(self, group: int) -> sublime.View | None: ... - def on_capability_added_async(self, registration_id: str, capability_path: str, options: Dict[str, Any]) -> None: + def on_capability_added_async(self, registration_id: str, capability_path: str, options: dict[str, Any]) -> None: ... - def on_capability_removed_async(self, registration_id: str, discarded_capabilities: Dict[str, Any]) -> None: + def on_capability_removed_async(self, registration_id: str, discarded_capabilities: dict[str, Any]) -> None: ... def has_capability_async(self, capability_path: str) -> bool: @@ -571,7 +571,7 @@ def shutdown_async(self) -> None: ... def present_diagnostics_async( - self, is_view_visible: bool, data_per_severity: Dict[Tuple[int, bool], DiagnosticSeverityData] + self, is_view_visible: bool, data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] ) -> None: ... @@ -581,7 +581,7 @@ def on_request_started_async(self, request_id: int, request: Request) -> None: def on_request_finished_async(self, request_id: int) -> None: ... - def on_request_progress(self, request_id: int, params: Dict[str, Any]) -> None: + def on_request_progress(self, request_id: int, params: dict[str, Any]) -> None: ... def get_resolved_code_lenses_for_region(self, region: sublime.Region) -> Generator[CodeLensExtended, None, None]: @@ -611,13 +611,13 @@ def session_views(self) -> WeakSet[SessionViewProtocol]: ... @property - def version(self) -> Optional[int]: + def version(self) -> int | None: ... - def get_uri(self) -> Optional[str]: + def get_uri(self) -> str | None: ... - def get_language_id(self) -> Optional[str]: + def get_language_id(self) -> str | None: ... def get_view_in_group(self, group: int) -> sublime.View: @@ -628,7 +628,7 @@ def register_capability_async( registration_id: str, capability_path: str, registration_path: str, - options: Dict[str, Any] + options: dict[str, Any] ) -> None: ... @@ -640,18 +640,18 @@ def unregister_capability_async( ) -> None: ... - def get_capability(self, capability_path: str) -> Optional[Any]: + def get_capability(self, capability_path: str) -> Any | None: ... def has_capability(self, capability_path: str) -> bool: ... def on_diagnostics_async( - self, raw_diagnostics: List[Diagnostic], version: Optional[int], visible_session_views: Set[SessionViewProtocol] + self, raw_diagnostics: list[Diagnostic], version: int | None, visible_session_views: set[SessionViewProtocol] ) -> None: ... - def get_document_link_at_point(self, view: sublime.View, point: int) -> Optional[DocumentLink]: + def get_document_link_at_point(self, view: sublime.View, point: int) -> DocumentLink | None: ... def update_document_link(self, new_link: DocumentLink) -> None: @@ -663,7 +663,7 @@ def do_semantic_tokens_async(self, view: sublime.View) -> None: def set_semantic_tokens_pending_refresh(self, needs_refresh: bool = True) -> None: ... - def get_semantic_tokens(self) -> List[Any]: + def get_semantic_tokens(self) -> list[Any]: ... def do_inlay_hints_async(self, view: sublime.View) -> None: @@ -675,7 +675,7 @@ def set_inlay_hints_pending_refresh(self, needs_refresh: bool = True) -> None: def remove_inlay_hint_phantom(self, phantom_uuid: str) -> None: ... - def do_document_diagnostic_async(self, view: sublime.View, version: Optional[int] = None) -> None: + def do_document_diagnostic_async(self, view: sublime.View, version: int | None = None) -> None: ... def set_document_diagnostic_pending_refresh(self, needs_refresh: bool = True) -> None: @@ -690,15 +690,15 @@ class AbstractViewListener(metaclass=ABCMeta): hover_provider_count = 0 @abstractmethod - def session_async(self, capability: str, point: Optional[int] = None) -> Optional[Session]: + def session_async(self, capability: str, point: int | None = None) -> Session | None: raise NotImplementedError() @abstractmethod - def sessions_async(self, capability: Optional[str] = None) -> Generator[Session, None, None]: + def sessions_async(self, capability: str | None = None) -> Generator[Session, None, None]: raise NotImplementedError() @abstractmethod - def session_buffers_async(self, capability: Optional[str] = None) -> Generator[SessionBufferProtocol, None, None]: + def session_buffers_async(self, capability: str | None = None) -> Generator[SessionBufferProtocol, None, None]: raise NotImplementedError() @abstractmethod @@ -721,7 +721,7 @@ def on_session_shutdown_async(self, session: Session) -> None: def diagnostics_intersecting_region_async( self, region: sublime.Region - ) -> Tuple[List[Tuple[SessionBufferProtocol, List[Diagnostic]]], sublime.Region]: + ) -> tuple[list[tuple[SessionBufferProtocol, list[Diagnostic]]], sublime.Region]: raise NotImplementedError() @abstractmethod @@ -729,13 +729,13 @@ def diagnostics_touching_point_async( self, pt: int, max_diagnostic_severity_level: int = DiagnosticSeverity.Hint - ) -> Tuple[List[Tuple[SessionBufferProtocol, List[Diagnostic]]], sublime.Region]: + ) -> tuple[list[tuple[SessionBufferProtocol, list[Diagnostic]]], sublime.Region]: raise NotImplementedError() def diagnostics_intersecting_async( self, - region_or_point: Union[sublime.Region, int] - ) -> Tuple[List[Tuple[SessionBufferProtocol, List[Diagnostic]]], sublime.Region]: + region_or_point: sublime.Region | int + ) -> tuple[list[tuple[SessionBufferProtocol, list[Diagnostic]]], sublime.Region]: if isinstance(region_or_point, int): return self.diagnostics_touching_point_async(region_or_point) elif region_or_point.empty(): @@ -804,7 +804,7 @@ def name(cls) -> str: raise NotImplementedError() @classmethod - def configuration(cls) -> Tuple[sublime.Settings, str]: + 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 @@ -844,12 +844,12 @@ def configuration(cls) -> Tuple[sublime.Settings, str]: "command" should look like this: "command": ["$cache_path/LSP-foobar/server_binary", "--stdio"] """ name = cls.name() - basename = "LSP-{}.sublime-settings".format(name) - filepath = "Packages/LSP-{}/{}".format(name, basename) + basename = f"LSP-{name}.sublime-settings" + filepath = f"Packages/LSP-{name}/{basename}" return sublime.load_settings(basename), filepath @classmethod - def additional_variables(cls) -> Optional[Dict[str, str]]: + def additional_variables(cls) -> dict[str, str] | None: """ In addition to the above variables, add more variables here to be expanded. """ @@ -898,7 +898,7 @@ def install_or_update(cls) -> None: @classmethod def can_start(cls, window: sublime.Window, initiating_view: sublime.View, - workspace_folders: List[WorkspaceFolder], configuration: ClientConfig) -> Optional[str]: + workspace_folders: list[WorkspaceFolder], configuration: ClientConfig) -> 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 @@ -916,7 +916,7 @@ def can_start(cls, window: sublime.Window, initiating_view: sublime.View, @classmethod def on_pre_start(cls, window: sublime.Window, initiating_view: sublime.View, - workspace_folders: List[WorkspaceFolder], configuration: ClientConfig) -> Optional[str]: + workspace_folders: list[WorkspaceFolder], configuration: ClientConfig) -> 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 @@ -934,7 +934,7 @@ def on_pre_start(cls, window: sublime.Window, initiating_view: sublime.View, @classmethod def on_post_start(cls, window: sublime.Window, initiating_view: sublime.View, - workspace_folders: List[WorkspaceFolder], configuration: ClientConfig) -> None: + workspace_folders: list[WorkspaceFolder], configuration: ClientConfig) -> None: """ Callback invoked when the subprocess was just started. @@ -958,7 +958,7 @@ def should_ignore(cls, view: sublime.View) -> bool: return False @classmethod - def markdown_language_id_to_st_syntax_map(cls) -> Optional[MarkdownLangMap]: + 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. @@ -987,7 +987,7 @@ def on_settings_changed(self, settings: DottedDict) -> None: """ pass - def on_workspace_configuration(self, params: Dict, configuration: Any) -> Any: + def on_workspace_configuration(self, params: dict, configuration: Any) -> Any: """ Override to augment configuration returned for the workspace/configuration request. @@ -1059,7 +1059,7 @@ def on_session_buffer_changed_async(self, session_buffer: SessionBufferProtocol) """ pass - def on_session_end_async(self, exit_code: Optional[int], exception: Optional[Exception]) -> None: + 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 @@ -1074,10 +1074,10 @@ def on_session_end_async(self, exit_code: Optional[int], exception: Optional[Exc pass -_plugins: Dict[str, Tuple[Type[AbstractPlugin], SettingsRegistration]] = {} +_plugins: dict[str, tuple[type[AbstractPlugin], SettingsRegistration]] = {} -def _register_plugin_impl(plugin: Type[AbstractPlugin], notify_listener: bool) -> None: +def _register_plugin_impl(plugin: type[AbstractPlugin], notify_listener: bool) -> None: global _plugins name = plugin.name() if name in _plugins: @@ -1088,10 +1088,10 @@ def _register_plugin_impl(plugin: Type[AbstractPlugin], notify_listener: bool) - on_change = functools.partial(client_configs.update_external_config, name, settings, base_file) _plugins[name] = (plugin, SettingsRegistration(settings, on_change)) except Exception as ex: - exception_log('Failed to register plugin "{}"'.format(name), ex) + exception_log(f'Failed to register plugin "{name}"', ex) -def register_plugin(plugin: Type[AbstractPlugin], notify_listener: bool = True) -> None: +def register_plugin(plugin: type[AbstractPlugin], notify_listener: bool = True) -> None: """ Register an LSP plugin in LSP. @@ -1136,7 +1136,7 @@ def plugin_unloaded(): _register_plugin_impl(plugin, notify_listener) -def unregister_plugin(plugin: Type[AbstractPlugin]) -> None: +def unregister_plugin(plugin: type[AbstractPlugin]) -> None: """ Unregister an LSP plugin in LSP. @@ -1150,10 +1150,10 @@ def unregister_plugin(plugin: Type[AbstractPlugin]) -> None: _plugins.pop(name, None) client_configs.remove_external_config(name) except Exception as ex: - exception_log('Failed to unregister plugin "{}"'.format(name), ex) + exception_log(f'Failed to unregister plugin "{name}"', ex) -def get_plugin(name: str) -> Optional[Type[AbstractPlugin]]: +def get_plugin(name: str) -> type[AbstractPlugin] | None: global _plugins tup = _plugins.get(name, None) return tup[0] if tup else None @@ -1182,7 +1182,7 @@ def outgoing_notification(self, method: str, params: Any) -> None: pass @abstractmethod - def incoming_response(self, request_id: Optional[int], params: Any, is_error: bool) -> None: + def incoming_response(self, request_id: int | None, params: Any, is_error: bool) -> None: pass @abstractmethod @@ -1194,7 +1194,7 @@ def incoming_notification(self, method: str, params: Any, unhandled: bool) -> No pass -def print_to_status_bar(error: Dict[str, Any]) -> None: +def print_to_status_bar(error: dict[str, Any]) -> None: sublime.status_message(error["message"]) @@ -1214,7 +1214,7 @@ def __init__( registration_id: str, capability_path: str, registration_path: str, - options: Dict[str, Any] + options: dict[str, Any] ) -> None: self.registration_id = registration_id self.registration_path = registration_path @@ -1246,13 +1246,13 @@ def check_applicable(self, sb: SessionBufferProtocol) -> None: class Session(TransportCallbacks): - def __init__(self, manager: Manager, logger: Logger, workspace_folders: List[WorkspaceFolder], - config: ClientConfig, plugin_class: Optional[Type[AbstractPlugin]]) -> None: - self.transport: Optional[Transport] = None - self.working_directory: Optional[str] = None + def __init__(self, manager: Manager, logger: Logger, workspace_folders: list[WorkspaceFolder], + config: ClientConfig, plugin_class: type[AbstractPlugin] | None) -> None: + self.transport: Transport | None = None + self.working_directory: str | None = None self.request_id = 0 # Our request IDs are always integers. self._logger = logger - self._response_handlers: Dict[int, Tuple[Request, Callable, Optional[Callable[[Any], None]]]] = {} + self._response_handlers: dict[int, tuple[Request, Callable, Callable[[Any], None] | None]] = {} self.config = config self.config_status_message = '' self.manager = weakref.ref(manager) @@ -1260,23 +1260,23 @@ def __init__(self, manager: Manager, logger: Logger, workspace_folders: List[Wor self.state = ClientStates.STARTING self.capabilities = Capabilities() self.diagnostics = DiagnosticsStorage() - self.diagnostics_result_ids: Dict[DocumentUri, Optional[str]] = {} - self.workspace_diagnostics_pending_response: Optional[int] = None + self.diagnostics_result_ids: dict[DocumentUri, str | None] = {} + self.workspace_diagnostics_pending_response: int | None = None self.exiting = False - self._registrations: Dict[str, _RegistrationData] = {} - self._init_callback: Optional[InitCallback] = None - self._initialize_error: Optional[Tuple[int, Optional[Exception]]] = None + self._registrations: dict[str, _RegistrationData] = {} + self._init_callback: InitCallback | None = None + self._initialize_error: tuple[int, Exception | None] | None = None self._views_opened = 0 self._workspace_folders = workspace_folders self._session_views: WeakSet[SessionViewProtocol] = WeakSet() self._session_buffers: WeakSet[SessionBufferProtocol] = WeakSet() - self._progress: Dict[ProgressToken, Optional[WindowProgressReporter]] = {} + self._progress: dict[ProgressToken, WindowProgressReporter | None] = {} self._watcher_impl = get_file_watcher_implementation() - self._static_file_watchers: List[FileWatcher] = [] - self._dynamic_file_watchers: Dict[str, List[FileWatcher]] = {} + self._static_file_watchers: list[FileWatcher] = [] + self._dynamic_file_watchers: dict[str, list[FileWatcher]] = {} self._plugin_class = plugin_class - self._plugin: Optional[AbstractPlugin] = None - self._status_messages: Dict[str, str] = {} + self._plugin: AbstractPlugin | None = None + self._status_messages: dict[str, str] = {} self._semantic_tokens_map = get_semantic_tokens_map(config.semantic_tokens) def __getattr__(self, name: str) -> Any: @@ -1290,7 +1290,7 @@ def __getattr__(self, name: str) -> Any: raise AttributeError(name) # TODO: Create an assurance that the API doesn't change here as it can be used by plugins. - def get_workspace_folders(self) -> List[WorkspaceFolder]: + def get_workspace_folders(self) -> list[WorkspaceFolder]: return self._workspace_folders def uses_plugin(self) -> bool: @@ -1316,7 +1316,7 @@ def session_views_async(self) -> Generator[SessionViewProtocol, None, None]: """ yield from self._session_views - def session_view_for_view_async(self, view: sublime.View) -> Optional[SessionViewProtocol]: + def session_view_for_view_async(self, view: sublime.View) -> SessionViewProtocol | None: for sv in self.session_views_async(): if sv.view == view: return sv @@ -1355,7 +1355,7 @@ def register_session_buffer_async(self, sb: SessionBufferProtocol) -> None: self._publish_diagnostics_to_session_buffer_async(sb, diagnostics, version=None) def _publish_diagnostics_to_session_buffer_async( - self, sb: SessionBufferProtocol, diagnostics: List[Diagnostic], version: Optional[int] + self, sb: SessionBufferProtocol, diagnostics: list[Diagnostic], version: int | None ) -> None: visible_session_views, _ = self.session_views_by_visibility() sb.on_diagnostics_async(diagnostics, version, visible_session_views) @@ -1369,11 +1369,11 @@ def session_buffers_async(self) -> Generator[SessionBufferProtocol, None, None]: """ yield from self._session_buffers - def get_session_buffer_for_uri_async(self, uri: DocumentUri) -> Optional[SessionBufferProtocol]: + def get_session_buffer_for_uri_async(self, uri: DocumentUri) -> SessionBufferProtocol | None: scheme, path = parse_uri(uri) if scheme == "file": - def compare_by_samefile(sb: Optional[SessionBufferProtocol]) -> bool: + def compare_by_samefile(sb: SessionBufferProtocol | None) -> bool: if not sb: return False candidate = sb.get_uri() @@ -1392,7 +1392,7 @@ def compare_by_samefile(sb: Optional[SessionBufferProtocol]) -> bool: predicate = compare_by_samefile else: - def compare_by_string(sb: Optional[SessionBufferProtocol]) -> bool: + def compare_by_string(sb: SessionBufferProtocol | None) -> bool: return sb.get_uri() == path if sb else False predicate = compare_by_string @@ -1400,7 +1400,7 @@ def compare_by_string(sb: Optional[SessionBufferProtocol]) -> bool: # --- capability observers ----------------------------------------------------------------------------------------- - def can_handle(self, view: sublime.View, scheme: str, capability: Optional[str], inside_workspace: bool) -> bool: + def can_handle(self, view: sublime.View, scheme: str, capability: str | None, inside_workspace: bool) -> bool: if not self.state == ClientStates.READY: return False if self._plugin and self._plugin.should_ignore(view): @@ -1428,7 +1428,7 @@ def has_capability(self, capability: str) -> bool: value = self.get_capability(capability) return value is not False and value is not None - def get_capability(self, capability: str) -> Optional[Any]: + def get_capability(self, capability: str) -> Any | None: if self.config.is_disabled_capability(capability): return None return self.capabilities.get(capability) @@ -1445,7 +1445,7 @@ def should_notify_did_change_workspace_folders(self) -> bool: def should_notify_will_save(self) -> bool: return self.capabilities.should_notify_will_save() - def should_notify_did_save(self) -> Tuple[bool, bool]: + def should_notify_did_save(self) -> tuple[bool, bool]: return self.capabilities.should_notify_did_save() def should_notify_did_close(self) -> bool: @@ -1453,8 +1453,8 @@ def should_notify_did_close(self) -> bool: # --- FileWatcherProtocol ------------------------------------------------------------------------------------------ - def on_file_event_async(self, events: List[FileWatcherEvent]) -> None: - changes: List[FileEvent] = [] + def on_file_event_async(self, events: list[FileWatcherEvent]) -> None: + changes: list[FileEvent] = [] for event in events: event_type, filepath = event changes.append({ @@ -1465,10 +1465,10 @@ def on_file_event_async(self, events: List[FileWatcherEvent]) -> None: # --- misc methods ------------------------------------------------------------------------------------------------- - def markdown_language_id_to_st_syntax_map(self) -> Optional[MarkdownLangMap]: + def markdown_language_id_to_st_syntax_map(self) -> MarkdownLangMap | None: return self._plugin.markdown_language_id_to_st_syntax_map() if self._plugin is not None else None - def handles_path(self, file_path: Optional[str], inside_workspace: bool) -> bool: + def handles_path(self, file_path: str | None, inside_workspace: bool) -> bool: if self._supports_workspace_folders(): # A workspace-aware language server handles any path, both inside and outside the workspaces. return True @@ -1484,7 +1484,7 @@ def handles_path(self, file_path: Optional[str], inside_workspace: bool) -> bool return True return False - def update_folders(self, folders: List[WorkspaceFolder]) -> None: + def update_folders(self, folders: list[WorkspaceFolder]) -> None: if self.should_notify_did_change_workspace_folders(): added, removed = diff(self._workspace_folders, folders) if added or removed: @@ -1502,8 +1502,8 @@ def update_folders(self, folders: List[WorkspaceFolder]) -> None: def initialize_async( self, - variables: Dict[str, str], - working_directory: Optional[str], + variables: dict[str, str], + working_directory: str | None, transport: Transport, init_callback: InitCallback ) -> None: @@ -1528,16 +1528,16 @@ def _handle_initialize_success(self, result: InitializeResult) -> None: self._maybe_send_did_change_configuration() execute_commands = self.get_capability('executeCommandProvider.commands') if execute_commands: - debug("{}: Supported execute commands: {}".format(self.config.name, execute_commands)) + debug(f"{self.config.name}: Supported execute commands: {execute_commands}") code_action_kinds = self.get_capability('codeActionProvider.codeActionKinds') if code_action_kinds: - debug('{}: supported code action kinds: {}'.format(self.config.name, code_action_kinds)) + debug(f'{self.config.name}: supported code action kinds: {code_action_kinds}') semantic_token_types = cast(List[str], self.get_capability('semanticTokensProvider.legend.tokenTypes')) if semantic_token_types: - debug('{}: Supported semantic token types: {}'.format(self.config.name, semantic_token_types)) + debug(f'{self.config.name}: Supported semantic token types: {semantic_token_types}') semantic_token_modifiers = cast(List[str], self.get_capability('semanticTokensProvider.legend.tokenModifiers')) if semantic_token_modifiers: - debug('{}: Supported semantic token modifiers: {}'.format(self.config.name, semantic_token_modifiers)) + debug(f'{self.config.name}: Supported semantic token modifiers: {semantic_token_modifiers}') if self._watcher_impl: config = self.config.file_watcher patterns = config.get('patterns') @@ -1559,7 +1559,7 @@ def _handle_initialize_error(self, result: InitializeError) -> None: # Init callback called after transport is closed to avoid pre-mature GC of Session. self.end_async() - def _get_global_ignore_globs(self, root_path: str) -> List[str]: + def _get_global_ignore_globs(self, root_path: str) -> list[str]: folder_exclude_patterns = cast(List[str], globalprefs().get('folder_exclude_patterns')) folder_excludes = [ sublime_pattern_to_glob(pattern, is_directory_pattern=True, root_path=root_path) @@ -1592,7 +1592,7 @@ def _maybe_send_did_change_configuration(self) -> None: resolved = self.config.settings.get_resolved(variables) self.send_notification(Notification("workspace/didChangeConfiguration", {"settings": resolved})) - def _template_variables(self) -> Dict[str, str]: + def _template_variables(self) -> dict[str, str]: variables = extract_variables(self.window) if self._plugin_class is not None: extra_vars = self._plugin_class.additional_variables() @@ -1601,7 +1601,7 @@ def _template_variables(self) -> Dict[str, str]: return variables def execute_command( - self, command: ExecuteCommandParams, progress: bool, view: Optional[sublime.View] = None + self, command: ExecuteCommandParams, progress: bool, view: sublime.View | None = None ) -> Promise: """Run a command from any thread. Your .then() continuations will run in Sublime's worker thread.""" if self._plugin: @@ -1638,7 +1638,7 @@ def run_async() -> None: ) def run_code_action_async( - self, code_action: Union[Command, CodeAction], progress: bool, view: Optional[sublime.View] = None + self, code_action: Command | CodeAction, progress: bool, view: sublime.View | None = None ) -> Promise: command = code_action.get("command") if isinstance(command, str): @@ -1659,10 +1659,10 @@ def run_code_action_async( def try_open_uri_async( self, uri: DocumentUri, - r: Optional[Range] = None, + r: Range | None = None, flags: int = 0, group: int = -1 - ) -> Optional[Promise[Optional[sublime.View]]]: + ) -> Promise[sublime.View | None] | None: if uri.startswith("file:"): return self._open_file_uri_async(uri, r, flags, group) # Try to find a pre-existing session-buffer @@ -1681,23 +1681,23 @@ def try_open_uri_async( def open_uri_async( self, uri: DocumentUri, - r: Optional[Range] = None, + r: Range | None = None, flags: int = 0, group: int = -1 - ) -> Promise[Optional[sublime.View]]: + ) -> Promise[sublime.View | None]: promise = self.try_open_uri_async(uri, r, flags, group) return Promise.resolve(None) if promise is None else promise def _open_file_uri_async( self, uri: DocumentUri, - r: Optional[Range] = None, + r: Range | None = None, flags: int = 0, group: int = -1 - ) -> Promise[Optional[sublime.View]]: - result: PackagedTask[Optional[sublime.View]] = Promise.packaged_task() + ) -> Promise[sublime.View | None]: + result: PackagedTask[sublime.View | None] = Promise.packaged_task() - def handle_continuation(view: Optional[sublime.View]) -> None: + def handle_continuation(view: sublime.View | None) -> None: if view and r: center_selection(view, r) sublime.set_timeout_async(lambda: result[1](view)) @@ -1709,16 +1709,16 @@ def _open_uri_with_plugin_async( self, plugin: AbstractPlugin, uri: DocumentUri, - r: Optional[Range], + r: Range | None, flags: int, group: int, - ) -> Optional[Promise[Optional[sublime.View]]]: + ) -> Promise[sublime.View | None] | None: # I cannot type-hint an unpacked tuple - pair: PackagedTask[Tuple[str, str, str]] = Promise.packaged_task() + pair: PackagedTask[tuple[str, str, str]] = Promise.packaged_task() # It'd be nice to have automatic tuple unpacking continuations callback = lambda a, b, c: pair[1]((a, b, c)) # noqa: E731 if plugin.on_open_uri_async(uri, callback): - result: PackagedTask[Optional[sublime.View]] = Promise.packaged_task() + result: PackagedTask[sublime.View | None] = Promise.packaged_task() def open_scratch_buffer(title: str, content: str, syntax: str) -> None: if group > -1: @@ -1739,8 +1739,8 @@ def open_scratch_buffer(title: str, content: str, syntax: str) -> None: return result[0] return None - def open_location_async(self, location: Union[Location, LocationLink], flags: int = 0, - group: int = -1) -> Promise[Optional[sublime.View]]: + def open_location_async(self, location: Location | LocationLink, flags: int = 0, + group: int = -1) -> Promise[sublime.View | None]: uri, r = get_uri_and_range_from_location(location) return self.open_uri_async(uri, r, flags, group) @@ -1749,8 +1749,8 @@ def notify_plugin_on_session_buffer_change(self, session_buffer: SessionBufferPr self._plugin.on_session_buffer_changed_async(session_buffer) def _maybe_resolve_code_action( - self, code_action: CodeAction, view: Optional[sublime.View] - ) -> Promise[Union[CodeAction, Error]]: + self, code_action: CodeAction, view: sublime.View | None + ) -> Promise[CodeAction | Error]: if "edit" not in code_action: has_capability = self.has_capability("codeActionProvider.resolveProvider") if not has_capability and view: @@ -1764,13 +1764,13 @@ def _maybe_resolve_code_action( return Promise.resolve(code_action) def _apply_code_action_async( - self, code_action: Union[CodeAction, Error, None], view: Optional[sublime.View] + self, code_action: CodeAction | Error | None, view: sublime.View | None ) -> Promise[None]: if not code_action: return Promise.resolve(None) if isinstance(code_action, Error): # TODO: our promise must be able to handle exceptions (or, wait until we can use coroutines) - self.window.status_message("Failed to apply code action: {}".format(code_action)) + self.window.status_message(f"Failed to apply code action: {code_action}") return Promise.resolve(None) edit = code_action.get("edit") promise = self.apply_workspace_edit_async(edit) if edit else Promise.resolve(None) @@ -1795,7 +1795,7 @@ def apply_workspace_edit_async(self, edit: WorkspaceEdit) -> Promise[None]: def apply_parsed_workspace_edits(self, changes: WorkspaceChanges) -> Promise[None]: active_sheet = self.window.active_sheet() selected_sheets = self.window.selected_sheets() - promises: List[Promise[None]] = [] + promises: list[Promise[None]] = [] for uri, (edits, view_version) in changes.items(): promises.append( self.open_uri_async(uri).then(functools.partial(self._apply_text_edits, edits, view_version, uri)) @@ -1805,35 +1805,35 @@ def apply_parsed_workspace_edits(self, changes: WorkspaceChanges) -> Promise[Non .then(lambda _: self._set_focused_sheet(active_sheet)) def _apply_text_edits( - self, edits: List[TextEdit], view_version: Optional[int], uri: str, view: Optional[sublime.View] + self, edits: list[TextEdit], view_version: int | None, uri: str, view: sublime.View | None ) -> None: if view is None or not view.is_valid(): - print('LSP: ignoring edits due to no view for uri: {}'.format(uri)) + print(f'LSP: ignoring edits due to no view for uri: {uri}') return apply_text_edits(view, edits, required_view_version=view_version) - def _set_selected_sheets(self, sheets: List[sublime.Sheet]) -> None: + def _set_selected_sheets(self, sheets: list[sublime.Sheet]) -> None: if len(sheets) > 1 and len(self.window.selected_sheets()) != len(sheets): self.window.select_sheets(sheets) - def _set_focused_sheet(self, sheet: Optional[sublime.Sheet]) -> None: + def _set_focused_sheet(self, sheet: sublime.Sheet | None) -> None: if sheet and sheet != self.window.active_sheet(): self.window.focus_sheet(sheet) def decode_semantic_token( self, - types_legend: Tuple[str, ...], - modifiers_legend: Tuple[str, ...], + types_legend: tuple[str, ...], + modifiers_legend: tuple[str, ...], token_type_encoded: int, token_modifiers_encoded: int - ) -> Tuple[str, List[str], Optional[str]]: + ) -> tuple[str, list[str], str | None]: return decode_semantic_token( types_legend, modifiers_legend, self._semantic_tokens_map, token_type_encoded, token_modifiers_encoded) - def session_views_by_visibility(self) -> Tuple[Set[SessionViewProtocol], Set[SessionViewProtocol]]: - visible_session_views: Set[SessionViewProtocol] = set() - not_visible_session_views: Set[SessionViewProtocol] = set() - selected_sheets: Set[sublime.Sheet] = set() + def session_views_by_visibility(self) -> tuple[set[SessionViewProtocol], set[SessionViewProtocol]]: + visible_session_views: set[SessionViewProtocol] = set() + not_visible_session_views: set[SessionViewProtocol] = set() + selected_sheets: set[sublime.Sheet] = set() for group in range(self.window.num_groups()): selected_sheets = selected_sheets.union(self.window.selected_sheets_in_group(group)) for sheet in self.window.sheets(): @@ -1856,7 +1856,7 @@ def do_workspace_diagnostics_async(self) -> None: # The server is probably leaving the request open intentionally, in order to continuously stream updates via # $/progress notifications. return - previous_result_ids: List[PreviousResultId] = [ + previous_result_ids: list[PreviousResultId] = [ {'uri': uri, 'value': result_id} for uri, result_id in self.diagnostics_result_ids.items() if result_id is not None ] @@ -1937,9 +1937,9 @@ def m_workspace_workspaceFolders(self, _: Any, request_id: Any) -> None: """handles the workspace/workspaceFolders request""" self.send_response(Response(request_id, [wf.to_lsp() for wf in self._workspace_folders])) - def m_workspace_configuration(self, params: Dict[str, Any], request_id: Any) -> None: + def m_workspace_configuration(self, params: dict[str, Any], request_id: Any) -> None: """handles the workspace/configuration request""" - items: List[Any] = [] + items: list[Any] = [] requested_items = params.get("items") or [] for requested_item in requested_items: configuration = self.config.settings.copy(requested_item.get('section') or None) @@ -2014,7 +2014,7 @@ def m_client_registerCapability(self, params: RegistrationParams, request_id: An capability_path, registration_path = method_to_capability(registration["method"]) if self.config.is_disabled_capability(capability_path): continue - debug("{}: registering capability:".format(self.config.name), capability_path) + debug(f"{self.config.name}: registering capability:", capability_path) options = registration.get("registerOptions") if not isinstance(options, dict): options = {} @@ -2038,7 +2038,7 @@ def m_client_registerCapability(self, params: RegistrationParams, request_id: An sublime.set_timeout_async(inform) if self._watcher_impl and capability_path == "didChangeWatchedFilesProvider": capability_options = cast(DidChangeWatchedFilesRegistrationOptions, options) - file_watchers: List[FileWatcher] = [] + file_watchers: list[FileWatcher] = [] for config in capability_options.get("watchers", []): pattern = config.get("globPattern", '') if not isinstance(pattern, str): @@ -2058,7 +2058,7 @@ def m_client_unregisterCapability(self, params: UnregistrationParams, request_id for unregistration in unregistrations: registration_id = unregistration["id"] capability_path, registration_path = method_to_capability(unregistration["method"]) - debug("{}: unregistering capability:".format(self.config.name), capability_path) + debug(f"{self.config.name}: unregistering capability:", capability_path) data = self._registrations.pop(registration_id, None) if self._watcher_impl and capability_path == "workspace.didChangeWatchedFiles": file_watchers = self._dynamic_file_watchers.pop(registration_id, None) @@ -2078,7 +2078,7 @@ def m_window_showDocument(self, params: Any, request_id: Any) -> None: """handles the window/showDocument request""" uri = params.get("uri") - def success(b: Union[None, bool, sublime.View]) -> None: + def success(b: None | bool | sublime.View) -> None: if isinstance(b, bool): pass elif isinstance(b, sublime.View): @@ -2110,7 +2110,7 @@ def _invoke_views(self, request: Request, method: str, *args: Any) -> None: def _create_window_progress_reporter(self, token: ProgressToken, value: WorkDoneProgressBegin) -> None: self._progress[token] = WindowProgressReporter( window=self.window, - key="lspprogress{}{}".format(self.config.name, token), + key=f"lspprogress{self.config.name}{token}", title=value["title"], message=value.get("message") ) @@ -2155,7 +2155,7 @@ def m___progress(self, params: ProgressParams) -> None: value = cast(WorkDoneProgressBegin, value) self._create_window_progress_reporter(token, value) return - debug('unknown $/progress token: {}'.format(token)) + debug(f'unknown $/progress token: {token}') return if kind == 'begin': value = cast(WorkDoneProgressBegin, value) @@ -2204,7 +2204,7 @@ def end_async(self) -> None: def _handle_shutdown_result(self, _: Any) -> None: self.exit() - def on_transport_close(self, exit_code: int, exception: Optional[Exception]) -> None: + def on_transport_close(self, exit_code: int, exception: Exception | None) -> None: self.exiting = True self.state = ClientStates.STOPPING self.transport = None @@ -2228,7 +2228,7 @@ def send_request_async( self, request: Request, on_result: Callable[[Any], None], - on_error: Optional[Callable[[Any], None]] = None + on_error: Callable[[Any], None] | None = None ) -> int: """You must call this method from Sublime's worker thread. Callbacks will run in Sublime's worker thread.""" self.request_id += 1 @@ -2249,7 +2249,7 @@ def send_request( self, request: Request, on_result: Callable[[Any], None], - on_error: Optional[Callable[[Any], None]] = None, + on_error: Callable[[Any], None] | None = None, ) -> None: """You can call this method from any thread. Callbacks will run in Sublime's worker thread.""" sublime.set_timeout_async(functools.partial(self.send_request_async, request, on_result, on_error)) @@ -2260,7 +2260,7 @@ def send_request_task(self, request: Request) -> Promise: self.send_request_async(request, resolver, lambda x: resolver(Error.from_lsp(x))) return promise - def send_request_task_2(self, request: Request) -> Tuple[Promise, int]: + def send_request_task_2(self, request: Request) -> tuple[Promise, int]: task: PackagedTask[Any] = Promise.packaged_task() promise, resolver = task request_id = self.send_request_async(request, resolver, lambda x: resolver(Error.from_lsp(x))) @@ -2293,7 +2293,7 @@ def exit(self) -> None: except AttributeError: pass - def send_payload(self, payload: Dict[str, Any]) -> None: + def send_payload(self, payload: dict[str, Any]) -> None: try: self.transport.send(payload) # type: ignore except AttributeError: @@ -2301,8 +2301,8 @@ def send_payload(self, payload: Dict[str, Any]) -> None: def deduce_payload( self, - payload: Dict[str, Any] - ) -> Tuple[Optional[Callable], Any, Optional[int], Optional[str], Optional[str]]: + payload: dict[str, Any] + ) -> tuple[Callable | None, Any, int | None, str | None, str | None]: if "method" in payload: method = payload["method"] handler = self._get_handler(method) @@ -2334,8 +2334,8 @@ def deduce_payload( debug("Unknown payload type: ", payload) return (None, None, None, None, None) - def on_payload(self, payload: Dict[str, Any]) -> None: - handler, result, req_id, typestr, method = self.deduce_payload(payload) + def on_payload(self, payload: dict[str, Any]) -> None: + handler, result, req_id, typestr, _method = self.deduce_payload(payload) if handler: try: if req_id is None: @@ -2351,16 +2351,16 @@ def on_payload(self, payload: Dict[str, Any]) -> None: self.send_error_response(req_id, Error.from_exception(ex)) raise except Exception as err: - exception_log("Error handling {}".format(typestr), err) + exception_log(f"Error handling {typestr}", err) def response_handler( self, response_id: int, - response: Dict[str, Any] - ) -> Tuple[Optional[Callable], Optional[str], Any, bool]: + response: dict[str, Any] + ) -> tuple[Callable | None, str | None, Any, bool]: request, handler, error_handler = self._response_handlers.pop(response_id, (None, None, None)) if not request: - error = {"code": ErrorCodes.InvalidParams, "message": "unknown response ID {}".format(response_id)} + error = {"code": ErrorCodes.InvalidParams, "message": f"unknown response ID {response_id}"} return (print_to_status_bar, None, error, True) self._invoke_views(request, "on_request_finished_async", response_id) if "result" in response and "error" not in response: @@ -2373,5 +2373,5 @@ def response_handler( error = {"code": ErrorCodes.InvalidParams, "message": "invalid response payload"} return (error_handler, request.method, error, True) - def _get_handler(self, method: str) -> Optional[Callable]: + def _get_handler(self, method: str) -> Callable | None: return getattr(self, method2attr(method), None) diff --git a/plugin/core/settings.py b/plugin/core/settings.py index 13b5daaff..eb171be2a 100644 --- a/plugin/core/settings.py +++ b/plugin/core/settings.py @@ -5,7 +5,7 @@ from .types import read_dict_setting from .types import Settings from .types import SettingsRegistration -from typing import Any, Callable, Dict, Optional +from typing import Any, Callable import os import sublime @@ -13,11 +13,11 @@ class ClientConfigs: def __init__(self) -> None: - self.all: Dict[str, ClientConfig] = {} - self.external: Dict[str, ClientConfig] = {} - self._listener: Optional[Callable[[Optional[str]], None]] = None + self.all: dict[str, ClientConfig] = {} + self.external: dict[str, ClientConfig] = {} + self._listener: Callable[[str | None], None] | None = None - def _notify_listener(self, config_name: Optional[str] = None) -> None: + def _notify_listener(self, config_name: str | None = None) -> None: if callable(self._listener): self._listener(config_name) @@ -60,7 +60,7 @@ def remove_external_config(self, name: str) -> None: def update_external_config(self, name: str, s: sublime.Settings, file: str) -> None: try: config = ClientConfig.from_sublime_settings(name, s, file) - except IOError: + except OSError: # The plugin is about to be disabled (for example by Package Control for an upgrade), let unregister_plugin # handle this return @@ -104,14 +104,14 @@ def enable(self, config_name: str) -> None: def disable(self, config_name: str) -> None: self._set_enabled(config_name, False) - def set_listener(self, recipient: Callable[[Optional[str]], None]) -> None: + def set_listener(self, recipient: Callable[[str | None], None]) -> None: self._listener = recipient -_settings_obj: Optional[sublime.Settings] = None -_settings: Optional[Settings] = None -_settings_registration: Optional[SettingsRegistration] = None -_global_settings: Optional[sublime.Settings] = None +_settings_obj: sublime.Settings | None = None +_settings: Settings | None = None +_settings_registration: SettingsRegistration | None = None +_global_settings: sublime.Settings | None = None client_configs = ClientConfigs() @@ -153,9 +153,9 @@ def globalprefs() -> sublime.Settings: return _global_settings # type: ignore -def read_client_config(name: str, d: Dict[str, Any]) -> ClientConfig: +def read_client_config(name: str, d: dict[str, Any]) -> ClientConfig: return ClientConfig.from_dict(name, d) -def update_client_config(external_config: ClientConfig, user_override_config: Dict[str, Any]) -> ClientConfig: +def update_client_config(external_config: ClientConfig, user_override_config: dict[str, Any]) -> ClientConfig: return ClientConfig.from_config(external_config, user_override_config) diff --git a/plugin/core/signature_help.py b/plugin/core/signature_help.py index 81a05ce05..84e0624ef 100644 --- a/plugin/core/signature_help.py +++ b/plugin/core/signature_help.py @@ -7,7 +7,6 @@ from .views import FORMAT_STRING from .views import MarkdownLangMap from .views import minihtml -from typing import List, Optional import functools import html import re @@ -42,7 +41,7 @@ class SigHelp: determined by what the end-user is doing. """ - def __init__(self, state: SignatureHelp, language_map: Optional[MarkdownLangMap]) -> None: + def __init__(self, state: SignatureHelp, language_map: MarkdownLangMap | None) -> None: self._state = state self._language_map = language_map self._signatures = self._state["signatures"] @@ -57,9 +56,9 @@ def __init__(self, state: SignatureHelp, language_map: Optional[MarkdownLangMap] @classmethod def from_lsp( cls, - sighelp: Optional[SignatureHelp], - language_map: Optional[MarkdownLangMap] - ) -> "Optional[SigHelp]": + sighelp: SignatureHelp | None, + language_map: MarkdownLangMap | None + ) -> SigHelp | None: """Create a SigHelp state object from a server's response to textDocument/signatureHelp.""" if sighelp is None or not sighelp.get("signatures"): return None @@ -71,7 +70,7 @@ def render(self, view: sublime.View) -> str: signature = self._signatures[self._active_signature_index] except IndexError: return "" - formatted: List[str] = [] + formatted: list[str] = [] if self.has_multiple_signatures(): formatted.append(self._render_intro()) self._function_color = view.style_for_scope("entity.name.function.sighelp.lsp")["foreground"] @@ -112,8 +111,8 @@ def _render_intro(self) -> str: len(self._signatures), ) - def _render_label(self, signature: SignatureInformation) -> List[str]: - formatted: List[str] = [] + def _render_label(self, signature: SignatureInformation) -> list[str]: + formatted: list[str] = [] # Note that this
class and the extra
 are copied from mdpopups' HTML output. When mdpopups changes
         # its output style, we must update this literal string accordingly.
         formatted.append('
')
@@ -132,10 +131,10 @@ def _render_label(self, signature: SignatureInformation) -> List[str]:
                     # route relies on the client being smart enough to figure where the parameter is inside of
                     # the signature label. The above case where the label is a tuple of (start, end) positions is much
                     # more robust.
-                    label_match = re.search(r"(? List[str]:
         formatted.append("
") return formatted - def _render_docs(self, view: sublime.View, signature: SignatureInformation) -> List[str]: - formatted: List[str] = [] + def _render_docs(self, view: sublime.View, signature: SignatureInformation) -> list[str]: + formatted: list[str] = [] docs = self._parameter_documentation(view, signature) if docs: formatted.append(docs) @@ -164,7 +163,7 @@ def _render_docs(self, view: sublime.View, signature: SignatureInformation) -> L formatted.append('
') return formatted - def _parameter_documentation(self, view: sublime.View, signature: SignatureInformation) -> Optional[str]: + def _parameter_documentation(self, view: sublime.View, signature: SignatureInformation) -> str | None: parameters = signature.get("parameters") if not parameters: return None @@ -178,7 +177,7 @@ def _parameter_documentation(self, view: sublime.View, signature: SignatureInfor return minihtml(view, documentation, allowed_formats, self._language_map) return None - def _signature_documentation(self, view: sublime.View, signature: SignatureInformation) -> Optional[str]: + def _signature_documentation(self, view: sublime.View, signature: SignatureInformation) -> str | None: documentation = signature.get("documentation") if documentation: allowed_formats = FORMAT_STRING | FORMAT_MARKUP_CONTENT @@ -196,9 +195,9 @@ def _parameter(self, content: str, active: bool) -> str: def _wrap_with_color(content: str, color: str, bold: bool = False, underline: bool = False) -> str: - style = 'color: {}'.format(color) + style = f'color: {color}' if bold: style += '; font-weight: bold' if underline: style += '; text-decoration: underline' - return '{}'.format(style, html.escape(content, quote=False)) + return f'{html.escape(content, quote=False)}' diff --git a/plugin/core/transports.py b/plugin/core/transports.py index b064bc067..b0e91f89c 100644 --- a/plugin/core/transports.py +++ b/plugin/core/transports.py @@ -5,7 +5,7 @@ from contextlib import closing from functools import partial from queue import Queue -from typing import Any, Callable, Dict, Generic, IO, List, Optional, Protocol, Tuple, TypeVar, Union +from typing import Any, Callable, Dict, Generic, IO, Protocol, TypeVar import http import json import os @@ -37,7 +37,7 @@ def close(self) -> None: class TransportCallbacks(Protocol[T_contra]): - def on_transport_close(self, exit_code: int, exception: Optional[Exception]) -> None: + def on_transport_close(self, exit_code: int, exception: Exception | None) -> None: ... def on_payload(self, payload: T_contra) -> None: @@ -52,17 +52,17 @@ class AbstractProcessor(Generic[T]): def write_data(self, writer: IO[bytes], data: T) -> None: raise NotImplementedError() - def read_data(self, reader: IO[bytes]) -> Optional[T]: + def read_data(self, reader: IO[bytes]) -> T | None: raise NotImplementedError() class JsonRpcProcessor(AbstractProcessor[Dict[str, Any]]): - def write_data(self, writer: IO[bytes], data: Dict[str, Any]) -> None: + def write_data(self, writer: IO[bytes], data: dict[str, Any]) -> None: body = self._encode(data) - writer.writelines(("Content-Length: {}\r\n\r\n".format(len(body)).encode('ascii'), body)) + writer.writelines((f"Content-Length: {len(body)}\r\n\r\n".encode('ascii'), body)) - def read_data(self, reader: IO[bytes]) -> Optional[Dict[str, Any]]: + def read_data(self, reader: IO[bytes]) -> dict[str, Any] | None: headers = http.client.parse_headers(reader) # type: ignore try: body = reader.read(int(headers.get("Content-Length"))) @@ -72,14 +72,14 @@ def read_data(self, reader: IO[bytes]) -> Optional[Dict[str, Any]]: raise StopLoopError() else: # Propagate server's output to the UI. - raise Exception("Unexpected payload in server's stdout:\n\n{}".format(headers)) + raise Exception(f"Unexpected payload in server's stdout:\n\n{headers}") try: return self._decode(body) except Exception as ex: - raise Exception("JSON decode error: {}".format(ex)) + raise Exception(f"JSON decode error: {ex}") @staticmethod - def _encode(data: Dict[str, Any]) -> bytes: + def _encode(data: dict[str, Any]) -> bytes: return json.dumps( data, ensure_ascii=False, @@ -89,14 +89,14 @@ def _encode(data: Dict[str, Any]) -> bytes: ).encode('utf-8') @staticmethod - def _decode(message: bytes) -> Dict[str, Any]: + def _decode(message: bytes) -> dict[str, Any]: return json.loads(message.decode('utf-8')) class ProcessTransport(Transport[T]): - def __init__(self, name: str, process: Optional[subprocess.Popen], socket: Optional[socket.socket], - reader: IO[bytes], writer: IO[bytes], stderr: Optional[IO[bytes]], + def __init__(self, name: str, process: subprocess.Popen | None, socket: socket.socket | None, + reader: IO[bytes], writer: IO[bytes], stderr: IO[bytes] | None, processor: AbstractProcessor[T], callback_object: TransportCallbacks[T]) -> None: self._closed = False self._process = process @@ -105,14 +105,14 @@ def __init__(self, name: str, process: Optional[subprocess.Popen], socket: Optio self._writer = writer self._stderr = stderr self._processor = processor - self._reader_thread = threading.Thread(target=self._read_loop, name='{}-reader'.format(name)) - self._writer_thread = threading.Thread(target=self._write_loop, name='{}-writer'.format(name)) + self._reader_thread = threading.Thread(target=self._read_loop, name=f'{name}-reader') + self._writer_thread = threading.Thread(target=self._write_loop, name=f'{name}-writer') self._callback_object = weakref.ref(callback_object) - self._send_queue: Queue[Union[T, None]] = Queue(0) + self._send_queue: Queue[T | None] = Queue(0) self._reader_thread.start() self._writer_thread.start() if stderr: - self._stderr_thread = threading.Thread(target=self._stderr_loop, name='{}-stderr'.format(name)) + self._stderr_thread = threading.Thread(target=self._stderr_loop, name=f'{name}-stderr') self._stderr_thread.start() def send(self, payload: T) -> None: @@ -131,7 +131,7 @@ def _join_thread(self, t: threading.Thread) -> None: try: t.join(2) except TimeoutError as ex: - exception_log("failed to join {} thread".format(t.name), ex) + exception_log(f"failed to join {t.name} thread", ex) def __del__(self) -> None: self.close() @@ -165,7 +165,7 @@ def invoke(p: T) -> None: else: self._send_queue.put_nowait(None) - def _end(self, exception: Optional[Exception]) -> None: + def _end(self, exception: Exception | None) -> None: exit_code = 0 if self._process: if not exception: @@ -195,7 +195,7 @@ def invoke() -> None: self.close() def _write_loop(self) -> None: - exception: Optional[Exception] = None + exception: Exception | None = None try: while self._writer: d = self._send_queue.get() @@ -234,8 +234,8 @@ def _stderr_loop(self) -> None: json_rpc_processor = JsonRpcProcessor() -def create_transport(config: TransportConfig, cwd: Optional[str], - callback_object: TransportCallbacks) -> Transport[Dict[str, Any]]: +def create_transport(config: TransportConfig, cwd: str | None, + callback_object: TransportCallbacks) -> Transport[dict[str, Any]]: if config.tcp_port is not None: assert config.tcp_port is not None if config.tcp_port < 0: @@ -246,8 +246,8 @@ def create_transport(config: TransportConfig, cwd: Optional[str], else: stdout = subprocess.PIPE stdin = subprocess.PIPE - sock: Optional[socket.socket] = None - process: Optional[subprocess.Popen] = None + sock: socket.socket | None = None + process: subprocess.Popen | None = None def start_subprocess() -> subprocess.Popen: startupinfo = _fixup_startup_args(config.command) @@ -269,14 +269,14 @@ def start_subprocess() -> subprocess.Popen: if config.tcp_port: sock = _connect_tcp(config.tcp_port) if sock is None: - raise RuntimeError("Failed to connect on port {}".format(config.tcp_port)) + raise RuntimeError(f"Failed to connect on port {config.tcp_port}") reader = sock.makefile('rwb') # type: ignore writer = reader else: reader = process.stdout # type: ignore writer = process.stdin # type: ignore if not reader or not writer: - raise RuntimeError('Failed initializing transport: reader: {}, writer: {}'.format(reader, writer)) + raise RuntimeError(f'Failed initializing transport: reader: {reader}, writer: {writer}') stderr = process.stderr if process else None return ProcessTransport( config.name, process, sock, reader, writer, stderr, json_rpc_processor, callback_object) # type: ignore @@ -300,7 +300,7 @@ def kill_all_subprocesses() -> None: pass -def _fixup_startup_args(args: List[str]) -> Any: +def _fixup_startup_args(args: list[str]) -> Any: startupinfo = None if sublime.platform() == "windows": startupinfo = subprocess.STARTUPINFO() # type: ignore @@ -321,15 +321,15 @@ def _fixup_startup_args(args: List[str]) -> Any: def _start_subprocess( - args: List[str], + args: list[str], stdin: int, stdout: int, stderr: int, startupinfo: Any, - env: Dict[str, str], - cwd: Optional[str] + env: dict[str, str], + cwd: str | None ) -> subprocess.Popen: - debug("starting {} in {}".format(args, cwd if cwd else os.getcwd())) + debug(f"starting {args} in {cwd if cwd else os.getcwd()}") process = subprocess.Popen( args=args, stdin=stdin, @@ -342,7 +342,7 @@ def _start_subprocess( return process -def _await_client_connection(listener_socket: socket.socket) -> Tuple[socket.socket, IO[bytes], IO[bytes]]: +def _await_client_connection(listener_socket: socket.socket) -> tuple[socket.socket, IO[bytes], IO[bytes]]: with closing(listener_socket): # Await one client connection (blocking!) sock, _ = listener_socket.accept() @@ -353,7 +353,7 @@ def _await_client_connection(listener_socket: socket.socket) -> Tuple[socket.soc def _start_subprocess_and_await_connection( listener_socket: socket.socket, subprocess_starter: Callable[[], subprocess.Popen] -) -> Tuple[subprocess.Popen, socket.socket, IO[bytes], IO[bytes]]: +) -> tuple[subprocess.Popen, socket.socket, IO[bytes], IO[bytes]]: process = None # We need to be able to start the process while also awaiting a client connection. @@ -372,7 +372,7 @@ def start_in_background() -> None: return process, sock, reader, writer # type: ignore -def _connect_tcp(port: int) -> Optional[socket.socket]: +def _connect_tcp(port: int) -> socket.socket | None: start_time = time.time() while time.time() - start_time < TCP_CONNECT_TIMEOUT: try: diff --git a/plugin/core/tree_view.py b/plugin/core/tree_view.py index 484485274..041cb2e4d 100644 --- a/plugin/core/tree_view.py +++ b/plugin/core/tree_view.py @@ -8,7 +8,7 @@ from abc import abstractmethod from enum import IntEnum from functools import partial -from typing import Dict, List, Optional, TypeVar +from typing import TypeVar import html import sublime import sublime_api # pyright: ignore[reportMissingImports] @@ -49,7 +49,7 @@ def __init__( self.id = str(uuid.uuid4()) def html(self, sheet_name: str, indent_level: int) -> str: - indent_html = ' '.format(indent_level) + indent_html = f' ' if self.collapsible_state == TreeItemCollapsibleState.COLLAPSED: disclosure_button_html = ''.format( sublime.command_url('lsp_expand_tree_item', {'name': sheet_name, 'id': self.id})) @@ -64,13 +64,12 @@ def html(self, sheet_name: str, indent_level: int) -> str: label_html = '{}'.format( self.command_url, html.escape(self.tooltip), html.escape(self.label)) elif self.command_url: - label_html = '{}'.format(self.command_url, html.escape(self.label)) + label_html = f'{html.escape(self.label)}' elif self.tooltip: - label_html = '{}'.format( - html.escape(self.tooltip), html.escape(self.label)) + label_html = f'{html.escape(self.label)}' else: - label_html = '{}'.format(html.escape(self.label)) - description_html = '{}'.format(html.escape(self.description)) if \ + label_html = f'{html.escape(self.label)}' + description_html = f'{html.escape(self.description)}' if \ self.description else '' return '
{}
'.format( indent_html + disclosure_button_html + icon_html + label_html + description_html) @@ -104,14 +103,14 @@ def __init__(self, element: T, tree_item: TreeItem, indent_level: int = 0) -> No self.element = element self.tree_item = tree_item self.indent_level = indent_level - self.child_ids: List[str] = [] + self.child_ids: list[str] = [] self.is_resolved = False class TreeDataProvider(metaclass=ABCMeta): @abstractmethod - def get_children(self, element: Optional[T]) -> Promise[List[T]]: + def get_children(self, element: T | None) -> Promise[list[T]]: """ Implement this to return the children for the given element or root (if no element is passed). """ raise NotImplementedError() @@ -127,8 +126,8 @@ class TreeViewSheet(sublime.HtmlSheet): def __init__(self, id: int, name: str, data_provider: TreeDataProvider, header: str = "") -> None: super().__init__(id) - self.nodes: Dict[str, Node] = {} - self.root_nodes: List[str] = [] + self.nodes: dict[str, Node] = {} + self.root_nodes: list[str] = [] self.name = name self.data_provider = data_provider self.header = header @@ -146,8 +145,8 @@ def set_provider(self, data_provider: TreeDataProvider, header: str = "") -> Non self.header = header self.data_provider.get_children(None).then(self._set_root_nodes) - def _set_root_nodes(self, elements: List[T]) -> None: - promises: List[Promise[None]] = [] + def _set_root_nodes(self, elements: list[T]) -> None: + promises: list[Promise[None]] = [] for element in elements: tree_item = self.data_provider.get_tree_item(element) tree_item.collapsible_state = TreeItemCollapsibleState.EXPANDED @@ -156,7 +155,7 @@ def _set_root_nodes(self, elements: List[T]) -> None: promises.append(self.data_provider.get_children(element).then(partial(self._add_children, tree_item.id))) Promise.all(promises).then(lambda _: self._update_contents()) - def _add_children(self, id: str, elements: List[T]) -> None: + def _add_children(self, id: str, elements: list[T]) -> None: assert id in self.nodes node = self.nodes[id] for element in elements: @@ -284,7 +283,7 @@ def new_tree_view_sheet( header: str = "", flags: int = 0, group: int = -1 -) -> Optional[TreeViewSheet]: +) -> TreeViewSheet | None: """ Use this function to create a new TreeView in form of a special HtmlSheet (TreeViewSheet). Only one TreeViewSheet with the given name is allowed per window. If there already exists a TreeViewSheet with the same name, its content diff --git a/plugin/core/types.py b/plugin/core/types.py index 074364ed8..50793d625 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -5,7 +5,7 @@ from .protocol import TextDocumentSyncKind from .url import filename_to_uri from .url import parse_uri -from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Set, Tuple, TypedDict, TypeVar, Union +from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, TypedDict, TypeVar, Union from typing import cast from wcmatch.glob import BRACE from wcmatch.glob import globmatch @@ -61,7 +61,7 @@ def runtime(token: str) -> Generator[None, None, None]: T = TypeVar("T") -def diff(old: Iterable[T], new: Iterable[T]) -> Tuple[Set[T], Set[T]]: +def diff(old: Iterable[T], new: Iterable[T]) -> tuple[set[T], set[T]]: """ Return a tuple of (added, removed) items """ @@ -83,20 +83,20 @@ def matches_pattern(path: str, patterns: Any) -> bool: return False -def sublime_pattern_to_glob(pattern: str, is_directory_pattern: bool, root_path: Optional[str] = None) -> str: +def sublime_pattern_to_glob(pattern: str, is_directory_pattern: bool, root_path: str | None = None) -> str: """ Convert a Sublime Text pattern (http://www.sublimetext.com/docs/file_patterns.html) to a glob pattern that utilizes globstar extension. """ glob = pattern if '/' not in glob: # basic pattern: compared against exact file or directory name - glob = '**/{}'.format(glob) + glob = f'**/{glob}' if is_directory_pattern: glob += '/**' else: # complex pattern # With '*/' prefix or '/*' suffix, the '*' matches '/' characters. if glob.startswith('*/'): - glob = '*{}'.format(glob) + glob = f'*{glob}' if glob.endswith('/*'): glob += '*' # If a pattern ends in '/' it will be treated as a directory pattern, and will match both a directory with that @@ -108,7 +108,7 @@ def sublime_pattern_to_glob(pattern: str, is_directory_pattern: bool, root_path: glob = posixpath.join(root_path, glob[2:]) # If a pattern begins with a single /, it will be treated as an absolute path. if not glob.startswith('/') and not glob.startswith('**/'): - glob = '**/{}'.format(glob) + glob = f'**/{glob}' if is_directory_pattern and not glob.endswith('/**'): glob += '/**' return glob @@ -242,7 +242,7 @@ def __init__(self, s: sublime.Settings) -> None: self.update(s) def update(self, s: sublime.Settings) -> None: - def r(name: str, default: Union[bool, int, str, list, dict]) -> None: + def r(name: str, default: bool | int | str | list | dict) -> None: val = s.get(name) setattr(self, name, val if isinstance(val, default.__class__) else default) @@ -322,7 +322,7 @@ def r(name: str, default: Union[bool, int, str, list, dict]) -> None: set_debug_logging(self.log_debug) - def highlight_style_region_flags(self, style_str: str) -> Tuple[int, int]: + def highlight_style_region_flags(self, style_str: str) -> tuple[int, int]: default = sublime.NO_UNDO if style_str in ("background", "fill"): # Backwards-compatible with "fill" style = default | sublime.DRAW_NO_OUTLINE @@ -335,7 +335,7 @@ def highlight_style_region_flags(self, style_str: str) -> Tuple[int, int]: return default | sublime.DRAW_NO_FILL, default | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.DRAW_SOLID_UNDERLINE # noqa: E501 @staticmethod - def _style_str_to_flag(style_str: str) -> Optional[int]: + def _style_str_to_flag(style_str: str) -> int | None: default = sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.DRAW_NO_FILL | sublime.NO_UNDO # This method could be a dict or lru_cache if style_str == "": @@ -351,13 +351,13 @@ def _style_str_to_flag(style_str: str) -> Optional[int]: # default style (includes NO_UNDO) return None - def diagnostics_highlight_style_flags(self) -> List[Optional[int]]: + def diagnostics_highlight_style_flags(self) -> list[int | None]: """Returns flags for highlighting diagnostics on single lines per severity""" if isinstance(self.diagnostics_highlight_style, str): # same style for all severity levels return [self._style_str_to_flag(self.diagnostics_highlight_style)] * 4 elif isinstance(self.diagnostics_highlight_style, dict): - flags: List[Optional[int]] = [] + flags: list[int | None] = [] for sev in ("error", "warning", "info", "hint"): user_style = self.diagnostics_highlight_style.get(sev) if user_style is None: # user did not provide a style @@ -393,9 +393,9 @@ class DocumentFilter: def __init__( self, - language: Optional[str] = None, - scheme: Optional[str] = None, - pattern: Optional[str] = None + language: str | None = None, + scheme: str | None = None, + pattern: str | None = None ) -> None: self.scheme = scheme self.pattern = pattern @@ -425,7 +425,7 @@ class DocumentSelector: __slots__ = ("filters",) - def __init__(self, document_selector: List[Dict[str, Any]]) -> None: + def __init__(self, document_selector: list[dict[str, Any]]) -> None: self.filters = [DocumentFilter(**document_filter) for document_filter in document_selector] def __bool__(self) -> bool: @@ -438,7 +438,7 @@ def matches(self, view: sublime.View) -> bool: # method -> (capability dotted path, optional registration dotted path) # these are the EXCEPTIONS. The general rule is: method foo/bar --> (barProvider, barProvider.id) -_METHOD_TO_CAPABILITY_EXCEPTIONS: Dict[str, Tuple[str, Optional[str]]] = { +_METHOD_TO_CAPABILITY_EXCEPTIONS: dict[str, tuple[str, str | None]] = { 'workspace/symbol': ('workspaceSymbolProvider', None), 'workspace/didChangeWorkspaceFolders': ('workspace.workspaceFolders', 'workspace.workspaceFolders.changeNotifications'), @@ -453,7 +453,7 @@ def matches(self, view: sublime.View) -> bool: } -def method_to_capability(method: str) -> Tuple[str, str]: +def method_to_capability(method: str) -> tuple[str, str]: """ Given a method, returns the corresponding capability path, and the associated path to stash the registration key. @@ -474,13 +474,13 @@ def method_to_capability(method: str) -> Tuple[str, str]: return capability_path, registration_path -def normalize_text_sync(textsync: Union[None, int, Dict[str, Any]]) -> Dict[str, Any]: +def normalize_text_sync(textsync: None | int | dict[str, Any]) -> dict[str, Any]: """ Brings legacy text sync capabilities to the most modern format """ - result: Dict[str, Any] = {} + result: dict[str, Any] = {} if isinstance(textsync, int): - change: Optional[Dict[str, Any]] = {"syncKind": textsync} + change: dict[str, Any] | None = {"syncKind": textsync} result["textDocumentSync"] = {"didOpen": {}, "save": {}, "didClose": {}, "change": change} elif isinstance(textsync, dict): new = {} @@ -527,7 +527,7 @@ def register( registration_id: str, capability_path: str, registration_path: str, - options: Dict[str, Any] + options: dict[str, Any] ) -> None: stored_registration_id = self.get(registration_path) if isinstance(stored_registration_id, str): @@ -541,7 +541,7 @@ def unregister( registration_id: str, capability_path: str, registration_path: str - ) -> Optional[Dict[str, Any]]: + ) -> dict[str, Any] | None: stored_registration_id = self.get(registration_path) if not isinstance(stored_registration_id, str): debug("stored registration ID at", registration_path, "is not a string") @@ -556,7 +556,7 @@ def unregister( self.remove(registration_path) return discarded - def assign(self, d: Dict[str, Any]) -> None: + def assign(self, d: dict[str, Any]) -> None: textsync = normalize_text_sync(d.pop("textDocumentSync", None)) super().assign(d) if textsync: @@ -575,7 +575,7 @@ def should_notify_did_change_workspace_folders(self) -> bool: def should_notify_will_save(self) -> bool: return "textDocumentSync.willSave" in self - def should_notify_did_save(self) -> Tuple[bool, bool]: + def should_notify_did_save(self) -> tuple[bool, bool]: save = self.get("textDocumentSync.save") if isinstance(save, bool): return save, False @@ -588,7 +588,7 @@ def should_notify_did_close(self) -> bool: return "textDocumentSync.didClose" in self -def _translate_path(path: str, source: str, destination: str) -> Tuple[str, bool]: +def _translate_path(path: str, source: str, destination: str) -> tuple[str, bool]: # TODO: Case-insensitive file systems. Maybe this problem needs a much larger refactor. Even Sublime Text doesn't # handle case-insensitive file systems correctly. There are a few other places where case-sensitivity matters, for # example when looking up the correct view for diagnostics, and when finding a view for goto-def. @@ -606,10 +606,10 @@ def __init__(self, local: str, remote: str) -> None: self._remote = remote @classmethod - def parse(cls, json: Any) -> "Optional[List[PathMap]]": + def parse(cls, json: Any) -> list[PathMap] | None: if not isinstance(json, list): return None - result: List[PathMap] = [] + result: list[PathMap] = [] for path_map in json: if not isinstance(path_map, dict): debug('path map entry is not an object') @@ -630,10 +630,10 @@ def __eq__(self, other: Any) -> bool: return False return self._local == other._local and self._remote == other._remote - def map_from_local_to_remote(self, uri: str) -> Tuple[str, bool]: + def map_from_local_to_remote(self, uri: str) -> tuple[str, bool]: return _translate_path(uri, self._local, self._remote) - def map_from_remote_to_local(self, uri: str) -> Tuple[str, bool]: + def map_from_remote_to_local(self, uri: str) -> tuple[str, bool]: return _translate_path(uri, self._remote, self._local) @@ -643,10 +643,10 @@ class TransportConfig: def __init__( self, name: str, - command: List[str], - tcp_port: Optional[int], - env: Dict[str, str], - listener_socket: Optional[socket.socket] + command: list[str], + tcp_port: int | None, + env: dict[str, str], + listener_socket: socket.socket | None ) -> None: if not command and not tcp_port: raise ValueError('neither "command" nor "tcp_port" is provided; cannot start a language server') @@ -661,27 +661,27 @@ class ClientConfig: def __init__(self, name: str, selector: str, - priority_selector: Optional[str] = None, - schemes: Optional[List[str]] = None, - command: Optional[List[str]] = None, - binary_args: Optional[List[str]] = None, # DEPRECATED - tcp_port: Optional[int] = None, - auto_complete_selector: Optional[str] = None, + priority_selector: str | None = None, + schemes: list[str] | None = None, + command: list[str] | None = None, + binary_args: list[str] | None = None, # DEPRECATED + tcp_port: int | None = None, + auto_complete_selector: str | None = None, enabled: bool = True, init_options: DottedDict = DottedDict(), settings: DottedDict = DottedDict(), - env: Dict[str, Union[str, List[str]]] = {}, - experimental_capabilities: Optional[Dict[str, Any]] = None, + env: dict[str, str | list[str]] = {}, + experimental_capabilities: dict[str, Any] | None = None, disabled_capabilities: DottedDict = DottedDict(), file_watcher: FileWatcherConfig = {}, - semantic_tokens: Optional[Dict[str, str]] = None, + semantic_tokens: dict[str, str] | None = None, diagnostics_mode: str = "open_files", - path_maps: Optional[List[PathMap]] = None) -> None: + path_maps: list[PathMap] | None = None) -> None: self.name = name self.selector = selector self.priority_selector = priority_selector if priority_selector else self.selector if isinstance(schemes, list): - self.schemes: List[str] = schemes + self.schemes: list[str] = schemes else: self.schemes = ["file"] if isinstance(command, list): @@ -699,12 +699,12 @@ def __init__(self, self.disabled_capabilities = disabled_capabilities self.file_watcher = file_watcher self.path_maps = path_maps - self.status_key = "lsp_{}".format(self.name) + self.status_key = f"lsp_{self.name}" self.semantic_tokens = semantic_tokens self.diagnostics_mode = diagnostics_mode @classmethod - def from_sublime_settings(cls, name: str, s: sublime.Settings, file: str) -> "ClientConfig": + def from_sublime_settings(cls, name: str, s: sublime.Settings, file: str) -> ClientConfig: base = sublime.decode_value(sublime.load_resource(file)) settings = DottedDict(base.get("settings", {})) # defined by the plugin author settings.update(read_dict_setting(s, "settings", {})) # overrides from the user @@ -739,7 +739,7 @@ def from_sublime_settings(cls, name: str, s: sublime.Settings, file: str) -> "Cl ) @classmethod - def from_dict(cls, name: str, d: Dict[str, Any]) -> "ClientConfig": + def from_dict(cls, name: str, d: dict[str, Any]) -> ClientConfig: disabled_capabilities = d.get("disabled_capabilities") if isinstance(disabled_capabilities, dict): disabled_capabilities = DottedDict(disabled_capabilities) @@ -769,7 +769,7 @@ def from_dict(cls, name: str, d: Dict[str, Any]) -> "ClientConfig": ) @classmethod - def from_config(cls, src_config: "ClientConfig", override: Dict[str, Any]) -> "ClientConfig": + def from_config(cls, src_config: ClientConfig, override: dict[str, Any]) -> ClientConfig: path_map_override = PathMap.parse(override.get("path_maps")) disabled_capabilities = override.get("disabled_capabilities") if isinstance(disabled_capabilities, dict): @@ -798,9 +798,9 @@ def from_config(cls, src_config: "ClientConfig", override: Dict[str, Any]) -> "C path_maps=path_map_override if path_map_override else src_config.path_maps ) - def resolve_transport_config(self, variables: Dict[str, str]) -> TransportConfig: - tcp_port: Optional[int] = None - listener_socket: Optional[socket.socket] = None + def resolve_transport_config(self, variables: dict[str, str]) -> TransportConfig: + tcp_port: int | None = None + listener_socket: socket.socket | None = None if self.tcp_port is not None: # < 0 means we're hosting a TCP server if self.tcp_port < 0: @@ -831,7 +831,7 @@ def resolve_transport_config(self, variables: Dict[str, str]) -> TransportConfig def set_view_status(self, view: sublime.View, message: str) -> None: if sublime.load_settings("LSP.sublime-settings").get("show_view_status"): - status = "{} ({})".format(self.name, message) if message else self.name + status = f"{self.name} ({message})" if message else self.name view.set_status(self.status_key, status) def erase_view_status(self, view: sublime.View) -> None: @@ -857,7 +857,7 @@ def map_client_path_to_server_uri(self, path: str) -> str: def map_server_uri_to_client_path(self, uri: str) -> str: scheme, path = parse_uri(uri) if scheme not in ("file", "res"): - raise ValueError("{}: {} URI scheme is unsupported".format(uri, scheme)) + raise ValueError(f"{uri}: {scheme} URI scheme is unsupported") if self.path_maps: for path_map in self.path_maps: path, mapped = path_map.map_from_remote_to_local(path) @@ -878,18 +878,18 @@ def is_disabled_capability(self, capability_path: str) -> bool: return True return False - def filter_out_disabled_capabilities(self, capability_path: str, options: Dict[str, Any]) -> Dict[str, Any]: - result: Dict[str, Any] = {} + def filter_out_disabled_capabilities(self, capability_path: str, options: dict[str, Any]) -> dict[str, Any]: + result: dict[str, Any] = {} for k, v in options.items(): - if not self.is_disabled_capability("{}.{}".format(capability_path, k)): + if not self.is_disabled_capability(f"{capability_path}.{k}"): result[k] = v return result def __repr__(self) -> str: - items: List[str] = [] + items: list[str] = [] for k, v in self.__dict__.items(): if not k.startswith("_"): - items.append("{}={}".format(k, repr(v))) + items.append(f"{k}={repr(v)}") return "{}({})".format(self.__class__.__name__, ", ".join(items)) def __eq__(self, other: Any) -> bool: @@ -901,7 +901,7 @@ def __eq__(self, other: Any) -> bool: return True -def syntax2scope(syntax_path: str) -> Optional[str]: +def syntax2scope(syntax_path: str) -> str | None: syntax = sublime.syntax_from_path(syntax_path) return syntax.scope if syntax else None @@ -913,7 +913,7 @@ def view2scope(view: sublime.View) -> str: return '' -def _read_selector(config: Union[sublime.Settings, Dict[str, Any]]) -> str: +def _read_selector(config: sublime.Settings | dict[str, Any]) -> str: # Best base scenario, selector = config.get("selector") if isinstance(selector, str): @@ -939,7 +939,7 @@ def _read_selector(config: Union[sublime.Settings, Dict[str, Any]]) -> str: # No syntaxes and no document_selector... then there must exist a languageId. language_id = language.get("languageId") if isinstance(language_id, str): - selectors.append("source.{}".format(language_id)) + selectors.append(f"source.{language_id}") return "|".join(map("({})".format, selectors)) # Otherwise, look for "document_selector" document_selector = config.get("document_selector") @@ -957,11 +957,11 @@ def _read_selector(config: Union[sublime.Settings, Dict[str, Any]]) -> str: # No syntaxes and no document_selector... then there must exist a languageId. language_id = config.get("languageId") if language_id: - return "source.{}".format(language_id) + return f"source.{language_id}" return "" -def _read_priority_selector(config: Union[sublime.Settings, Dict[str, Any]]) -> str: +def _read_priority_selector(config: sublime.Settings | dict[str, Any]) -> str: # Best case scenario selector = config.get("priority_selector") if isinstance(selector, str): @@ -984,7 +984,7 @@ def _read_priority_selector(config: Union[sublime.Settings, Dict[str, Any]]) -> # No scopes and no feature_selector. So there must be a languageId language_id = language.get("languageId") if isinstance(language_id, str): - selectors.append("source.{}".format(language_id)) + selectors.append(f"source.{language_id}") return "|".join(map("({})".format, selectors)) # Otherwise, look for "feature_selector" feature_selector = config.get("feature_selector") @@ -997,7 +997,7 @@ def _read_priority_selector(config: Union[sublime.Settings, Dict[str, Any]]) -> # No scopes and no feature_selector... then there must exist a languageId language_id = config.get("languageId") if language_id: - return "source.{}".format(language_id) + return f"source.{language_id}" return "" @@ -1008,7 +1008,7 @@ def _find_free_port() -> int: return s.getsockname()[1] -def _start_tcp_listener(tcp_port: Optional[int]) -> socket.socket: +def _start_tcp_listener(tcp_port: int | None) -> socket.socket: sock = socket.socket() sock.bind(('localhost', tcp_port or 0)) sock.settimeout(TCP_CONNECT_TIMEOUT) diff --git a/plugin/core/url.py b/plugin/core/url.py index fc78c7091..eeff0cde7 100644 --- a/plugin/core/url.py +++ b/plugin/core/url.py @@ -1,5 +1,5 @@ from __future__ import annotations -from typing import Any, Tuple +from typing import Any from urllib.parse import urljoin from urllib.parse import urlparse from urllib.request import pathname2url @@ -27,7 +27,7 @@ def filename_to_uri(file_name: str) -> str: def view_to_uri(view: sublime.View) -> str: file_name = view.file_name() if not file_name: - return "buffer:{}".format(view.buffer_id()) + return f"buffer:{view.buffer_id()}" return filename_to_uri(file_name) @@ -41,7 +41,7 @@ def uri_to_filename(uri: str) -> str: return path -def parse_uri(uri: str) -> Tuple[str, str]: +def parse_uri(uri: str) -> tuple[str, str]: """ Parses an URI into a tuple where the first element is the URI scheme. The second element is the local filesystem path if the URI is a file URI, @@ -57,7 +57,7 @@ def parse_uri(uri: str) -> Tuple[str, str]: path = re.sub(r"^([a-z]):", _uppercase_driveletter, path) if netloc: # Convert to UNC path - return parsed.scheme, "\\\\{}\\{}".format(netloc, path) + return parsed.scheme, f"\\\\{netloc}\\{path}" else: return parsed.scheme, path return parsed.scheme, path @@ -67,7 +67,7 @@ def parse_uri(uri: str) -> Tuple[str, str]: return parsed.scheme, uri -def unparse_uri(parsed_uri: Tuple[str, str]) -> str: +def unparse_uri(parsed_uri: tuple[str, str]) -> str: """ Reverse of `parse_uri()`. """ @@ -81,11 +81,11 @@ def _to_resource_uri(path: str, prefix: str) -> str: See: https://github.com/sublimehq/sublime_text/issues/3742 """ - return "res:/Packages{}".format(pathname2url(path[len(prefix):])) + return f"res:/Packages{pathname2url(path[len(prefix):])}" def _uppercase_driveletter(match: Any) -> str: """ For compatibility with Sublime's VCS status in the status bar. """ - return "{}:".format(match.group(1).upper()) + return f"{match.group(1).upper()}:" diff --git a/plugin/core/views.py b/plugin/core/views.py index 6b8baae2e..3a6e8e8d1 100644 --- a/plugin/core/views.py +++ b/plugin/core/views.py @@ -41,7 +41,7 @@ from .types import ClientConfig from .url import parse_uri from .workspace import is_subpath_of -from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, Iterable, Tuple from typing import cast import html import itertools @@ -58,7 +58,7 @@ _baseflags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.DRAW_EMPTY_AS_OVERWRITE | sublime.NO_UNDO _multilineflags = sublime.DRAW_NO_FILL | sublime.NO_UNDO -DIAGNOSTIC_SEVERITY: List[Tuple[str, str, str, str, int, int]] = [ +DIAGNOSTIC_SEVERITY: list[tuple[str, str, str, str, int, int]] = [ # Kind CSS class Scope for color Icon resource add_regions flags for single-line diagnostic multi-line diagnostic # noqa: E501 ("error", "errors", "region.redish markup.error.lsp", "Packages/LSP/icons/error.png", _baseflags | sublime.DRAW_SQUIGGLY_UNDERLINE, _multilineflags), # noqa: E501 ("warning", "warnings", "region.yellowish markup.warning.lsp", "Packages/LSP/icons/warning.png", _baseflags | sublime.DRAW_SQUIGGLY_UNDERLINE, _multilineflags), # noqa: E501 @@ -72,9 +72,9 @@ class DiagnosticSeverityData: __slots__ = ('regions', 'regions_with_tag', 'annotations', 'scope', 'icon') def __init__(self, severity: int) -> None: - self.regions: List[sublime.Region] = [] - self.regions_with_tag: Dict[int, List[sublime.Region]] = {} - self.annotations: List[str] = [] + self.regions: list[sublime.Region] = [] + self.regions_with_tag: dict[int, list[sublime.Region]] = {} + self.annotations: list[str] = [] _, _, self.scope, self.icon, _, _ = DIAGNOSTIC_SEVERITY[severity - 1] if userprefs().diagnostics_gutter_marker != "sign": self.icon = "" if severity == DiagnosticSeverity.Hint else userprefs().diagnostics_gutter_marker @@ -85,7 +85,7 @@ def __init__(self, uri: str) -> None: self.uri = uri def __str__(self) -> str: - return "invalid URI scheme: {}".format(self.uri) + return f"invalid URI scheme: {self.uri}" def get_line(window: sublime.Window, file_name: str, row: int, strip: bool = True) -> str: @@ -117,7 +117,7 @@ def get_storage_path() -> str: return os.path.abspath(os.path.join(sublime.cache_path(), "..", "Package Storage")) -def extract_variables(window: sublime.Window) -> Dict[str, str]: +def extract_variables(window: sublime.Window) -> dict[str, str]: variables = window.extract_variables() variables["storage_path"] = get_storage_path() variables["cache_path"] = sublime.cache_path() @@ -171,7 +171,7 @@ def to_encoded_filename(path: str, position: Position) -> str: return '{}:{}:{}'.format(path, position['line'] + 1, position['character'] + 1) -def get_uri_and_range_from_location(location: Union[Location, LocationLink]) -> Tuple[DocumentUri, Range]: +def get_uri_and_range_from_location(location: Location | LocationLink) -> tuple[DocumentUri, Range]: if "targetUri" in location: location = cast(LocationLink, location) uri = location["targetUri"] @@ -183,7 +183,7 @@ def get_uri_and_range_from_location(location: Union[Location, LocationLink]) -> return uri, r -def get_uri_and_position_from_location(location: Union[Location, LocationLink]) -> Tuple[DocumentUri, Position]: +def get_uri_and_position_from_location(location: Location | LocationLink) -> tuple[DocumentUri, Position]: if "targetUri" in location: location = cast(LocationLink, location) uri = location["targetUri"] @@ -195,7 +195,7 @@ def get_uri_and_position_from_location(location: Union[Location, LocationLink]) return uri, position -def location_to_encoded_filename(location: Union[Location, LocationLink]) -> str: +def location_to_encoded_filename(location: Location | LocationLink) -> str: """ DEPRECATED """ @@ -209,7 +209,7 @@ def location_to_encoded_filename(location: Union[Location, LocationLink]) -> str class MissingUriError(Exception): def __init__(self, view_id: int) -> None: - super().__init__("View {} has no URI".format(view_id)) + super().__init__(f"View {view_id} has no URI") self.view_id = view_id @@ -220,7 +220,7 @@ def uri_from_view(view: sublime.View) -> DocumentUri: raise MissingUriError(view.id()) -def text_document_identifier(view_or_uri: Union[DocumentUri, sublime.View]) -> TextDocumentIdentifier: +def text_document_identifier(view_or_uri: DocumentUri | sublime.View) -> TextDocumentIdentifier: if isinstance(view_or_uri, DocumentUri): uri = view_or_uri else: @@ -228,7 +228,7 @@ def text_document_identifier(view_or_uri: Union[DocumentUri, sublime.View]) -> T return {"uri": uri} -def first_selection_region(view: sublime.View) -> Optional[sublime.Region]: +def first_selection_region(view: sublime.View) -> sublime.Region | None: try: return view.sel()[0] except IndexError: @@ -285,9 +285,9 @@ def render_text_change(change: sublime.TextChange) -> TextDocumentContentChangeE def did_change_text_document_params( - view: sublime.View, version: int, changes: Optional[Iterable[sublime.TextChange]] = None + view: sublime.View, version: int, changes: Iterable[sublime.TextChange] | None = None ) -> DidChangeTextDocumentParams: - content_changes: List[TextDocumentContentChangeEvent] = [] + content_changes: list[TextDocumentContentChangeEvent] = [] result: DidChangeTextDocumentParams = { "textDocument": versioned_text_document_identifier(view, version), "contentChanges": content_changes @@ -303,13 +303,13 @@ def did_change_text_document_params( def will_save_text_document_params( - view_or_uri: Union[DocumentUri, sublime.View], reason: TextDocumentSaveReason + view_or_uri: DocumentUri | sublime.View, reason: TextDocumentSaveReason ) -> WillSaveTextDocumentParams: return {"textDocument": text_document_identifier(view_or_uri), "reason": reason} def did_save_text_document_params( - view: sublime.View, include_text: bool, uri: Optional[DocumentUri] = None + view: sublime.View, include_text: bool, uri: DocumentUri | None = None ) -> DidSaveTextDocumentParams: result: DidSaveTextDocumentParams = { "textDocument": text_document_identifier(uri if uri is not None else view) @@ -328,7 +328,7 @@ def did_open(view: sublime.View, language_id: str) -> Notification: def did_change(view: sublime.View, version: int, - changes: Optional[Iterable[sublime.TextChange]] = None) -> Notification: + changes: Iterable[sublime.TextChange] | None = None) -> Notification: return Notification.didChange(did_change_text_document_params(view, version, changes)) @@ -340,7 +340,7 @@ def will_save_wait_until(view: sublime.View, reason: TextDocumentSaveReason) -> return Request.willSaveWaitUntil(will_save_text_document_params(view, reason), view) -def did_save(view: sublime.View, include_text: bool, uri: Optional[DocumentUri] = None) -> Notification: +def did_save(view: sublime.View, include_text: bool, uri: DocumentUri | None = None) -> Notification: return Notification.didSave(did_save_text_document_params(view, include_text, uri)) @@ -348,7 +348,7 @@ def did_close(uri: DocumentUri) -> Notification: return Notification.didClose(did_close_text_document_params(uri)) -def formatting_options(settings: sublime.Settings) -> Dict[str, Any]: +def formatting_options(settings: sublime.Settings) -> dict[str, Any]: # Build 4085 allows "trim_trailing_white_space_on_save" to be a string so we have to account for that in a # backwards-compatible way. trim_trailing_white_space = settings.get("trim_trailing_white_space_on_save") not in (False, None, "none") @@ -399,8 +399,8 @@ def selection_range_params(view: sublime.View) -> SelectionRangeParams: def text_document_code_action_params( view: sublime.View, region: sublime.Region, - diagnostics: List[Diagnostic], - only_kinds: Optional[List[CodeActionKind]] = None, + diagnostics: list[Diagnostic], + only_kinds: list[CodeActionKind] | None = None, manual: bool = False ) -> CodeActionParams: context: CodeActionContext = { @@ -427,16 +427,16 @@ def show_lsp_popup( location: int = -1, md: bool = False, flags: int = 0, - css: Optional[str] = None, - wrapper_class: Optional[str] = None, - body_id: Optional[str] = None, - on_navigate: Optional[Callable[..., None]] = None, - on_hide: Optional[Callable[..., None]] = None + css: str | None = None, + wrapper_class: str | None = None, + body_id: str | None = None, + on_navigate: Callable[..., None] | None = None, + on_hide: Callable[..., None] | None = None ) -> None: css = css if css is not None else lsp_css().popups wrapper_class = wrapper_class if wrapper_class is not None else lsp_css().popups_classname contents += LSP_POPUP_SPACER_HTML - body_wrapper = '{{}}'.format(body_id) if body_id else '{}' + body_wrapper = f'{{}}' if body_id else '{}' mdpopups.show_popup( view, body_wrapper.format(contents), @@ -456,14 +456,14 @@ def update_lsp_popup( contents: str, *, md: bool = False, - css: Optional[str] = None, - wrapper_class: Optional[str] = None, - body_id: Optional[str] = None + css: str | None = None, + wrapper_class: str | None = None, + body_id: str | None = None ) -> None: css = css if css is not None else lsp_css().popups wrapper_class = wrapper_class if wrapper_class is not None else lsp_css().popups_classname contents += LSP_POPUP_SPACER_HTML - body_wrapper = '{{}}'.format(body_id) if body_id else '{}' + body_wrapper = f'{{}}' if body_id else '{}' mdpopups.update_popup(view, body_wrapper.format(contents), css=css, md=md, wrapper_class=wrapper_class) @@ -474,9 +474,9 @@ def update_lsp_popup( def minihtml( view: sublime.View, - content: Union[MarkedString, MarkupContent, List[MarkedString]], + content: MarkedString | MarkupContent | list[MarkedString], allowed_formats: int, - language_id_map: Optional[MarkdownLangMap] = None + language_id_map: MarkdownLangMap | None = None ) -> str: """ Formats provided input content into markup accepted by minihtml. @@ -523,7 +523,7 @@ def minihtml( language = item.get("language") if language: - formatted.append("```{}\n{}\n```\n".format(language, value)) + formatted.append(f"```{language}\n{value}\n```\n") else: formatted.append(value) @@ -541,9 +541,9 @@ def minihtml( if parse_marked_string and language: # MarkedString (dict) is_plain_text = False - result = "```{}\n{}\n```\n".format(language, value) + result = f"```{language}\n{value}\n```\n" if is_plain_text: - return "

{}

".format(text2html(result)) if result else '' + return f"

{text2html(result)}

" if result else '' else: frontmatter = { "allow_code_wrap": True, @@ -592,7 +592,7 @@ def _replace_match(match: Any) -> str: return REPLACEMENT_MAP[special_match] url = match.group('url') if url: - return "{}".format(url, url) + return f"{url}" return len(match.group('multispace')) * ' ' @@ -600,28 +600,28 @@ def text2html(content: str) -> str: return re.sub(REPLACEMENT_RE, _replace_match, content) -def make_link(href: str, text: Any, class_name: Optional[str] = None, tooltip: Optional[str] = None) -> str: - link = " str: + link = f"{text}" return link def make_command_link( command: str, text: str, - command_args: Optional[Dict[str, Any]] = None, - class_name: Optional[str] = None, - tooltip: Optional[str] = None, - view_id: Optional[int] = None + command_args: dict[str, Any] | None = None, + class_name: str | None = None, + tooltip: str | None = None, + view_id: int | None = None ) -> str: if view_id is not None: cmd = "lsp_run_text_command_helper" - args: Optional[Dict[str, Any]] = {"view_id": view_id, "command": command, "args": command_args} + args: dict[str, Any] | None = {"view_id": view_id, "command": command, "args": command_args} else: cmd = command args = command_args @@ -629,7 +629,7 @@ def make_command_link( class LspRunTextCommandHelperCommand(sublime_plugin.WindowCommand): - def run(self, view_id: int, command: str, args: Optional[Dict[str, Any]] = None) -> None: + def run(self, view_id: int, command: str, args: dict[str, Any] | None = None) -> None: view = sublime.View(view_id) if view.is_valid(): view.run_command(command, args) @@ -662,8 +662,8 @@ def color_to_hex(color: Color) -> str: blue = round(color['blue'] * 255) alpha_dec = color['alpha'] if alpha_dec < 1: - return "#{:02x}{:02x}{:02x}{:02x}".format(red, green, blue, round(alpha_dec * 255)) - return "#{:02x}{:02x}{:02x}".format(red, green, blue) + return f"#{red:02x}{green:02x}{blue:02x}{round(alpha_dec * 255):02x}" + return f"#{red:02x}{green:02x}{blue:02x}" def lsp_color_to_html(color_info: ColorInformation) -> str: @@ -691,8 +691,8 @@ def diagnostic_severity(diagnostic: Diagnostic) -> DiagnosticSeverity: def format_diagnostics_for_annotation( - diagnostics: List[Diagnostic], severity: DiagnosticSeverity, view: sublime.View -) -> Tuple[List[str], str]: + diagnostics: list[Diagnostic], severity: DiagnosticSeverity, view: sublime.View +) -> tuple[list[str], str]: css_class = DIAGNOSTIC_SEVERITY[severity - 1][1] scope = DIAGNOSTIC_SEVERITY[severity - 1][2] color = view.style_for_scope(scope).get('foreground') or 'red' @@ -700,14 +700,14 @@ def format_diagnostics_for_annotation( for diagnostic in diagnostics: message = text2html(diagnostic.get('message') or '') source = diagnostic.get('source') - line = "[{}] {}".format(text2html(source), message) if source else message - content = '
{3}
'.format( - lsp_css().annotations, lsp_css().annotations_classname, css_class, line) + line = f"[{text2html(source)}] {message}" if source else message + content = '
{}
'.format( + lsp_css().annotations_classname, lsp_css().annotations, css_class, line) annotations.append(content) return (annotations, color) -def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[int], Optional[str], Optional[str]]: +def format_diagnostic_for_panel(diagnostic: Diagnostic) -> tuple[str, int | None, str | None, str | None]: """ Turn an LSP diagnostic into a string suitable for an output panel. @@ -727,7 +727,7 @@ def format_diagnostic_for_panel(diagnostic: Diagnostic) -> Tuple[str, Optional[i ) if formatted != "" or code is not None: # \u200B is the zero-width space - result += " \u200B{}".format(formatted) + result += f" \u200B{formatted}" offset = len(result) if href else None for line in itertools.islice(lines, 1, None): result += "\n" + 18 * " " + line @@ -738,10 +738,10 @@ def format_diagnostic_source_and_code(diagnostic: Diagnostic) -> str: formatted, code, href = diagnostic_source_and_code(diagnostic) if href is None or code is None: return formatted - return formatted + "({})".format(code) + return formatted + f"({code})" -def diagnostic_source_and_code(diagnostic: Diagnostic) -> Tuple[str, Optional[str], Optional[str]]: +def diagnostic_source_and_code(diagnostic: Diagnostic) -> tuple[str, str | None, str | None]: formatted = diagnostic.get("source", "") href = None code = diagnostic.get("code") @@ -751,14 +751,14 @@ def diagnostic_source_and_code(diagnostic: Diagnostic) -> Tuple[str, Optional[st if code_description: href = code_description["href"] else: - formatted += "({})".format(code) + formatted += f"({code})" return formatted, code, href def location_to_human_readable( config: ClientConfig, - base_dir: Optional[str], - location: Union[Location, LocationLink] + base_dir: str | None, + location: Location | LocationLink ) -> str: """ Format an LSP Location (or LocationLink) into a string suitable for a human to read @@ -780,7 +780,7 @@ def location_to_human_readable( return fmt.format(pathname, position["line"] + 1) -def location_to_href(config: ClientConfig, location: Union[Location, LocationLink]) -> str: +def location_to_href(config: ClientConfig, location: Location | LocationLink) -> str: """ Encode an LSP Location (or LocationLink) into a string suitable as a hyperlink in minihtml """ @@ -788,7 +788,7 @@ def location_to_href(config: ClientConfig, location: Union[Location, LocationLin return "location:{}@{}#{},{}".format(config.name, uri, position["line"], position["character"]) -def unpack_href_location(href: str) -> Tuple[str, str, int, int]: +def unpack_href_location(href: str) -> tuple[str, str, int, int]: """ Return the session name, URI, row, and col_utf16 from an encoded href. """ @@ -808,7 +808,7 @@ def is_location_href(href: str) -> bool: def _format_diagnostic_related_info( config: ClientConfig, info: DiagnosticRelatedInformation, - base_dir: Optional[str] = None + base_dir: str | None = None ) -> str: location = info["location"] return '{}: {}'.format( @@ -818,15 +818,15 @@ def _format_diagnostic_related_info( ) -def _html_element(name: str, text: str, class_name: Optional[str] = None, escape: bool = True) -> str: +def _html_element(name: str, text: str, class_name: str | None = None, escape: bool = True) -> str: return '<{0}{2}>{1}'.format( name, text2html(text) if escape else text, - ' class="{}"'.format(text2html(class_name)) if class_name else '' + f' class="{text2html(class_name)}"' if class_name else '' ) -def format_diagnostic_for_html(config: ClientConfig, diagnostic: Diagnostic, base_dir: Optional[str] = None) -> str: +def format_diagnostic_for_html(config: ClientConfig, diagnostic: Diagnostic, base_dir: str | None = None) -> str: html = _html_element('span', diagnostic["message"]) code = diagnostic.get("code") source = diagnostic.get("source") @@ -848,9 +848,9 @@ def format_diagnostic_for_html(config: ClientConfig, diagnostic: Diagnostic, bas def format_code_actions_for_quick_panel( - session_actions: Iterable[Tuple[str, Union[CodeAction, Command]]] -) -> Tuple[List[sublime.QuickPanelItem], int]: - items: List[sublime.QuickPanelItem] = [] + session_actions: Iterable[tuple[str, CodeAction | Command]] +) -> tuple[list[sublime.QuickPanelItem], int]: + items: list[sublime.QuickPanelItem] = [] selected_index = -1 for idx, (config_name, code_action) in enumerate(session_actions): lsp_kind = code_action.get("kind", "") diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 52d54dbcc..29abdea97 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -39,7 +39,7 @@ from datetime import datetime from subprocess import CalledProcessError from time import perf_counter -from typing import Any, Deque, Dict, Generator, List, Optional, Tuple, TYPE_CHECKING +from typing import Any, Generator, TYPE_CHECKING from weakref import ref from weakref import WeakSet import functools @@ -63,7 +63,7 @@ def set_diagnostics_count(view: sublime.View, errors: int, warnings: int) -> Non try: key = AbstractViewListener.TOTAL_ERRORS_AND_WARNINGS_STATUS_KEY if userprefs().show_diagnostics_count_in_view_status: - view.set_status(key, "E: {}, W: {}".format(errors, warnings)) + view.set_status(key, f"E: {errors}, W: {warnings}") else: view.erase_status(key) except Exception: @@ -77,15 +77,15 @@ def __init__(self, window: sublime.Window, workspace: ProjectFolders, config_man self._config_manager = config_manager self._sessions: WeakSet[Session] = WeakSet() self._workspace = workspace - self._pending_listeners: Deque[AbstractViewListener] = deque() + self._pending_listeners: deque[AbstractViewListener] = deque() self._listeners: WeakSet[AbstractViewListener] = WeakSet() - self._new_listener: Optional[AbstractViewListener] = None - self._new_session: Optional[Session] = None - self._panel_code_phantoms: Optional[sublime.PhantomSet] = None - self._server_log: List[Tuple[str, str]] = [] - self.panel_manager: Optional[PanelManager] = PanelManager(self._window) - self.tree_view_sheets: Dict[str, TreeViewSheet] = {} - self.formatters: Dict[str, str] = {} + self._new_listener: AbstractViewListener | None = None + self._new_session: Session | None = None + self._panel_code_phantoms: sublime.PhantomSet | None = None + self._server_log: list[tuple[str, str]] = [] + self.panel_manager: PanelManager | None = PanelManager(self._window) + self.tree_view_sheets: dict[str, TreeViewSheet] = {} + self.formatters: dict[str, str] = {} self.suppress_sessions_restart_on_project_update = False self.total_error_count = 0 self.total_warning_count = 0 @@ -97,7 +97,7 @@ def __init__(self, window: sublime.Window, workspace: ProjectFolders, config_man def window(self) -> sublime.Window: return self._window - def get_and_clear_server_log(self) -> List[Tuple[str, str]]: + def get_and_clear_server_log(self) -> list[tuple[str, str]]: log = self._server_log self._server_log = [] return log @@ -145,14 +145,14 @@ def unregister_listener_async(self, listener: AbstractViewListener) -> None: def listeners(self) -> Generator[AbstractViewListener, None, None]: yield from self._listeners - def listener_for_view(self, view: sublime.View) -> Optional[AbstractViewListener]: + def listener_for_view(self, view: sublime.View) -> AbstractViewListener | None: for listener in self.listeners(): if listener.view == view: return listener return None def _dequeue_listener_async(self) -> None: - listener: Optional[AbstractViewListener] = None + listener: AbstractViewListener | None = None if self._new_listener is not None: listener = self._new_listener # debug("re-checking listener", listener) @@ -196,10 +196,10 @@ def _publish_sessions_to_listener_async(self, listener: AbstractViewListener) -> try: listener.on_session_initialized_async(session) except Exception as ex: - message = "failed to register session {} to listener {}".format(session.config.name, listener) + message = f"failed to register session {session.config.name} to listener {listener}" exception_log(message, ex) - def sessions(self, view: sublime.View, capability: Optional[str] = None) -> Generator[Session, None, None]: + def sessions(self, view: sublime.View, capability: str | None = None) -> Generator[Session, None, None]: inside_workspace = self._workspace.contains(view) sessions = list(self._sessions) uri = view.settings().get("lsp_uri") @@ -210,20 +210,20 @@ def sessions(self, view: sublime.View, capability: Optional[str] = None) -> Gene if session.can_handle(view, scheme, capability, inside_workspace): yield session - def get_session(self, config_name: str, file_path: str) -> Optional[Session]: + def get_session(self, config_name: str, file_path: str) -> Session | None: return self._find_session(config_name, file_path) def _can_start_config(self, config_name: str, file_path: str) -> bool: return not bool(self._find_session(config_name, file_path)) - def _find_session(self, config_name: str, file_path: str) -> Optional[Session]: + def _find_session(self, config_name: str, file_path: str) -> Session | None: inside = self._workspace.contains(file_path) for session in self._sessions: if session.config.name == config_name and session.handles_path(file_path, inside): return session return None - def _needed_config(self, view: sublime.View) -> Optional[ClientConfig]: + def _needed_config(self, view: sublime.View) -> ClientConfig | None: configs = self._config_manager.match_view(view) handled = False file_name = view.file_name() @@ -252,7 +252,7 @@ def start_async(self, config: ClientConfig, initiating_view: sublime.View) -> No workspace_folders = sorted_workspace_folders(self._workspace.folders, file_path) plugin_class = get_plugin(config.name) variables = extract_variables(self._window) - cwd: Optional[str] = None + cwd: str | None = None if plugin_class is not None: if plugin_class.needs_update_or_installation(): config.set_view_status(initiating_view, "installing...") @@ -263,7 +263,7 @@ def start_async(self, config: ClientConfig, initiating_view: sublime.View) -> No cannot_start_reason = plugin_class.can_start(self._window, initiating_view, workspace_folders, config) if cannot_start_reason: config.erase_view_status(initiating_view) - message = "cannot start {}: {}".format(config.name, cannot_start_reason) + message = f"cannot start {config.name}: {cannot_start_reason}" self._config_manager.disable_config(config.name, only_for_session=True) # Continue with handling pending listeners self._new_session = None @@ -273,7 +273,7 @@ def start_async(self, config: ClientConfig, initiating_view: sublime.View) -> No config.set_view_status(initiating_view, "starting...") session = Session(self, self._create_logger(config.name), workspace_folders, config, plugin_class) if cwd: - transport_cwd: Optional[str] = cwd + transport_cwd: str | None = cwd else: transport_cwd = workspace_folders[0].path if workspace_folders else None transport_config = config.resolve_transport_config(variables) @@ -294,7 +294,7 @@ def start_async(self, config: ClientConfig, initiating_view: sublime.View) -> No "Re-enable by running \"LSP: Enable Language Server In Project\" from the Command Palette.", "\n\n--- Error: ---\n{1}" )).format(config.name, str(e)) - exception_log("Unable to start subprocess for {}".format(config.name), e) + exception_log(f"Unable to start subprocess for {config.name}", e) if isinstance(e, CalledProcessError): print("Server output:\n{}".format(e.output.decode('utf-8', 'replace'))) self._config_manager.disable_config(config.name, only_for_session=True) @@ -322,7 +322,7 @@ def _create_logger(self, config_name: str) -> Logger: loggers = [] for logger_type in userprefs().log_server: if logger_type not in logger_map: - debug("Invalid logger type ({}) specified for log_server settings".format(logger_type)) + debug(f"Invalid logger type ({logger_type}) specified for log_server settings") continue loggers.append(logger_map[logger_type]) if len(loggers) == 0: @@ -340,29 +340,29 @@ def handle_message_request(self, session: Session, params: Any, request_id: Any) if view: MessageRequestHandler(view, session, request_id, params, session.config.name).show() - def restart_sessions_async(self, config_name: Optional[str] = None) -> None: + def restart_sessions_async(self, config_name: str | None = None) -> None: self._end_sessions_async(config_name) listeners = list(self._listeners) self._listeners.clear() for listener in listeners: self.register_listener_async(listener) - def _end_sessions_async(self, config_name: Optional[str] = None) -> None: + def _end_sessions_async(self, config_name: str | None = None) -> None: sessions = list(self._sessions) for session in sessions: if config_name is None or config_name == session.config.name: session.end_async() self._sessions.discard(session) - def get_project_path(self, file_path: str) -> Optional[str]: - candidate: Optional[str] = None + def get_project_path(self, file_path: str) -> str | None: + candidate: str | None = None for folder in self._workspace.folders: if file_path.startswith(folder): if candidate is None or len(folder) > len(candidate): candidate = folder return candidate - def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> Optional[str]: + def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> str | None: scheme, path = parse_uri(uri) if scheme != "file": return None @@ -383,7 +383,7 @@ def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfi return "matches a project's folder_exclude_patterns" return None - def on_post_exit_async(self, session: Session, exit_code: int, exception: Optional[Exception]) -> None: + def on_post_exit_async(self, session: Session, exit_code: int, exception: Exception | None) -> None: self._sessions.discard(session) for listener in self._listeners: listener.on_session_shutdown_async(session) @@ -398,7 +398,7 @@ def on_post_exit_async(self, session: Session, exit_code: int, exception: Option "Re-enable by running \"LSP: Enable Language Server In Project\" from the Command Palette." )).format(config.name, RETRY_MAX_COUNT, int(RETRY_COUNT_TIMEDELTA.total_seconds())) if exception: - msg += "\n\n--- Error: ---\n{}".format(str(exception)) + msg += f"\n\n--- Error: ---\n{str(exception)}" restart = sublime.ok_cancel_dialog(msg, "Restart") if restart: for listener in self._listeners: @@ -428,9 +428,9 @@ def handle_log_message(self, session: Session, params: LogMessageParams) -> None MessageType.Debug: "DEBUG" }.get(message_type, "?") message = params['message'] - print("{}: {}: {}".format(session.config.name, level, message)) + print(f"{session.config.name}: {level}: {message}") if message_type == MessageType.Error: - self.window.status_message("{}: {}".format(session.config.name, message)) + self.window.status_message(f"{session.config.name}: {message}") def handle_stderr_log(self, session: Session, message: str) -> None: self.handle_server_message_async(session.config.name, message) @@ -456,7 +456,7 @@ def is_log_lines_limit_enabled(self) -> bool: return bool(panel and panel.settings().get(LOG_LINES_LIMIT_SETTING_NAME, True)) def handle_show_message(self, session: Session, params: Any) -> None: - sublime.status_message("{}: {}".format(session.config.name, extract_message(params))) + sublime.status_message(f"{session.config.name}: {extract_message(params)}") def on_diagnostics_updated(self) -> None: self.total_error_count = 0 @@ -471,11 +471,11 @@ def on_diagnostics_updated(self) -> None: self.update_diagnostics_panel_async() def update_diagnostics_panel_async(self) -> None: - to_render: List[str] = [] - prephantoms: List[Tuple[int, int, str, str]] = [] + to_render: list[str] = [] + prephantoms: list[tuple[int, int, str, str]] = [] row = 0 max_severity = userprefs().diagnostics_panel_include_severity_level - contributions: OrderedDict[str, List[Tuple[str, Optional[int], Optional[str], Optional[str]]]] = OrderedDict() + contributions: OrderedDict[str, list[tuple[str, int | None, str | None, str | None]]] = OrderedDict() for session in self._sessions: for (_, path), contribution in session.diagnostics.filter_map_diagnostics_async( is_severity_included(max_severity), lambda _, diagnostic: format_diagnostic_for_panel(diagnostic)): @@ -484,7 +484,7 @@ def update_diagnostics_panel_async(self) -> None: if not seen: contributions.move_to_end(path) for path, contribution in contributions.items(): - to_render.append("{}:".format(path)) + to_render.append(f"{path}:") row += 1 for content, offset, code, href in contribution: to_render.append(content) @@ -498,33 +498,33 @@ def update_diagnostics_panel_async(self) -> None: characters = _NO_DIAGNOSTICS_PLACEHOLDER sublime.set_timeout(functools.partial(self._update_panel_main_thread, characters, prephantoms)) - def _update_panel_main_thread(self, characters: str, prephantoms: List[Tuple[int, int, str, str]]) -> None: + def _update_panel_main_thread(self, characters: str, prephantoms: list[tuple[int, int, str, str]]) -> None: panel = self.panel_manager and self.panel_manager.ensure_diagnostics_panel() if not panel or not panel.is_valid(): return panel.run_command("lsp_update_panel", {"characters": characters}) if self._panel_code_phantoms is None: self._panel_code_phantoms = sublime.PhantomSet(panel, "hrefs") - phantoms: List[sublime.Phantom] = [] + phantoms: list[sublime.Phantom] = [] for row, col, code, href in prephantoms: point = panel.text_point(row, col) region = sublime.Region(point, point) - phantoms.append(sublime.Phantom(region, "({})".format(make_link(href, code)), sublime.LAYOUT_INLINE)) + phantoms.append(sublime.Phantom(region, f"({make_link(href, code)})", sublime.LAYOUT_INLINE)) self._panel_code_phantoms.update(phantoms) # --- Implements WindowConfigChangeListener ------------------------------------------------------------------------ - def on_configs_changed(self, config_name: Optional[str] = None) -> None: + def on_configs_changed(self, config_name: str | None = None) -> None: sublime.set_timeout_async(lambda: self.restart_sessions_async(config_name)) class WindowRegistry: def __init__(self) -> None: self._enabled = False - self._windows: Dict[int, WindowManager] = {} + self._windows: dict[int, WindowManager] = {} client_configs.set_listener(self._on_client_config_updated) - def _on_client_config_updated(self, config_name: Optional[str] = None) -> None: + def _on_client_config_updated(self, config_name: str | None = None) -> None: for wm in self._windows.values(): wm.get_config_manager().update(config_name) @@ -543,7 +543,7 @@ def disable(self) -> None: exception_log("failed to destroy window", ex) self._windows = {} - def lookup(self, window: Optional[sublime.Window]) -> Optional[WindowManager]: + def lookup(self, window: sublime.Window | None) -> WindowManager | None: if not self._enabled or not window or not window.is_valid(): return None wm = self._windows.get(window.id()) @@ -555,7 +555,7 @@ def lookup(self, window: Optional[sublime.Window]) -> Optional[WindowManager]: self._windows[window.id()] = manager return manager - def listener_for_view(self, view: sublime.View) -> Optional[AbstractViewListener]: + def listener_for_view(self, view: sublime.View) -> AbstractViewListener | None: manager = self.lookup(view.window()) if not manager: return None @@ -569,7 +569,7 @@ def discard(self, window: sublime.Window) -> None: class RequestTimeTracker: def __init__(self) -> None: - self._start_times: Dict[int, float] = {} + self._start_times: dict[int, float] = {} def start_tracking(self, request_id: int) -> None: self._start_times[request_id] = perf_counter() @@ -579,7 +579,7 @@ def end_tracking(self, request_id: int) -> str: if request_id in self._start_times: start = self._start_times.pop(request_id) duration_ms = perf_counter() - start - duration = '{}ms'.format(int(duration_ms * 1000)) + duration = f'{int(duration_ms * 1000)}ms' return duration @classmethod @@ -608,8 +608,8 @@ def run_on_async_worker_thread() -> None: nonlocal message params_str = repr(params) if 0 < userprefs().log_max_size <= len(params_str): - params_str = ''.format(len(params_str)) - message = "{}: {}".format(message, params_str) + params_str = f'' + message = f"{message}: {params_str}" manager = self._manager() if manager is not None: manager.handle_server_message_async(":", message) @@ -639,7 +639,7 @@ def outgoing_notification(self, method: str, params: Any) -> None: return self.log(self._format_notification(" ->", method), params) - def incoming_response(self, request_id: Optional[int], params: Any, is_error: bool) -> None: + def incoming_response(self, request_id: int | None, params: Any, is_error: bool) -> None: if not userprefs().log_server: return direction = "<~~" if is_error else "<<<" @@ -663,24 +663,23 @@ def _format_response(self, direction: str, request_id: Any, duration: str) -> st RequestTimeTracker.formatted_now(), direction, self._server_name, request_id, duration) def _format_request(self, direction: str, method: str, request_id: Any) -> str: - return "[{}] {} {} {} ({})".format( - RequestTimeTracker.formatted_now(), direction, self._server_name, method, request_id) + return f"[{RequestTimeTracker.formatted_now()}] {direction} {self._server_name} {method} ({request_id})" def _format_notification(self, direction: str, method: str) -> str: - return "[{}] {} {} {}".format(RequestTimeTracker.formatted_now(), direction, self._server_name, method) + return f"[{RequestTimeTracker.formatted_now()}] {direction} {self._server_name} {method}" class RemoteLogger(Logger): PORT = 9981 DIRECTION_OUTGOING = 1 DIRECTION_INCOMING = 2 - _ws_server: Optional[WebsocketServer] = None - _ws_server_thread: Optional[threading.Thread] = None + _ws_server: WebsocketServer | None = None + _ws_server_thread: threading.Thread | None = None _last_id = 0 def __init__(self, manager: WindowManager, server_name: str) -> None: RemoteLogger._last_id += 1 - self._server_name = '{} ({})'.format(server_name, RemoteLogger._last_id) + self._server_name = f'{server_name} ({RemoteLogger._last_id})' if not RemoteLogger._ws_server: try: RemoteLogger._ws_server = WebsocketServer(self.PORT) @@ -710,16 +709,16 @@ def _stop_server(self) -> None: RemoteLogger._ws_server_thread.join() RemoteLogger._ws_server_thread = None - def _on_new_client(self, client: Dict, server: WebsocketServer) -> None: + def _on_new_client(self, client: dict, server: WebsocketServer) -> None: """Called for every client connecting (after handshake).""" debug("New client connected and was given id %d" % client['id']) # server.send_message_to_all("Hey all, a new client has joined us") - def _on_client_left(self, client: Dict, server: WebsocketServer) -> None: + def _on_client_left(self, client: dict, server: WebsocketServer) -> None: """Called for every client disconnecting.""" debug("Client(%d) disconnected" % client['id']) - def _on_message_received(self, client: Dict, server: WebsocketServer, message: str) -> None: + def _on_message_received(self, client: dict, server: WebsocketServer, message: str) -> None: """Called when a client sends a message.""" debug("Client(%d) said: %s" % (client['id'], message)) @@ -743,7 +742,7 @@ def outgoing_request(self, request_id: int, method: str, params: Any) -> None: 'direction': self.DIRECTION_OUTGOING, }) - def incoming_response(self, request_id: Optional[int], params: Any, is_error: bool) -> None: + def incoming_response(self, request_id: int | None, params: Any, is_error: bool) -> None: self._broadcast_json({ 'server': self._server_name, 'id': request_id, @@ -801,7 +800,7 @@ def incoming_notification(self, method: str, params: Any, unhandled: bool) -> No 'direction': self.DIRECTION_INCOMING, }) - def _broadcast_json(self, data: Dict[str, Any]) -> None: + def _broadcast_json(self, data: dict[str, Any]) -> None: if RemoteLogger._ws_server: json_data = json.dumps(data, sort_keys=True, check_circular=False, separators=(',', ':')) RemoteLogger._ws_server.send_message_to_all(json_data) @@ -809,7 +808,7 @@ def _broadcast_json(self, data: Dict[str, Any]) -> None: class RouterLogger(Logger): def __init__(self) -> None: - self._loggers: List[Logger] = [] + self._loggers: list[Logger] = [] def append(self, logger: Logger) -> None: self._loggers.append(logger) diff --git a/plugin/core/workspace.py b/plugin/core/workspace.py index fb44f580f..e0d2b7a10 100644 --- a/plugin/core/workspace.py +++ b/plugin/core/workspace.py @@ -4,7 +4,7 @@ from .types import matches_pattern from .types import sublime_pattern_to_glob from .url import filename_to_uri -from typing import Any, List, Union +from typing import Any import sublime import os @@ -34,7 +34,7 @@ def __hash__(self) -> int: return hash((self.name, self.path)) def __repr__(self) -> str: - return "{}('{}', '{}')".format(self.__class__.__name__, self.name, self.path) + return f"{self.__class__.__name__}('{self.name}', '{self.path}')" def __str__(self) -> str: return self.path @@ -54,16 +54,16 @@ def includes_uri(self, uri: str) -> bool: return uri.startswith(self.uri()) -class ProjectFolders(object): +class ProjectFolders: def __init__(self, window: sublime.Window) -> None: self._window = window - self.folders: List[str] = self._window.folders() + self.folders: list[str] = self._window.folders() # Per-folder ignore patterns. The list order matches the order of self.folders. - self._folders_exclude_patterns: List[List[str]] = [] + self._folders_exclude_patterns: list[list[str]] = [] self._update_exclude_patterns(self.folders) - def _update_exclude_patterns(self, folders: List[str]) -> None: + def _update_exclude_patterns(self, folders: list[str]) -> None: # Ensure that the number of patterns matches the number of folders so that accessing by index never throws. self._folders_exclude_patterns = [[]] * len(folders) project_data = self._window.project_data() @@ -112,17 +112,17 @@ def includes_excluded_path(self, file_path: str) -> bool: break return is_excluded - def contains(self, view_or_file_name: Union[str, sublime.View]) -> bool: + def contains(self, view_or_file_name: str | sublime.View) -> bool: file_path = view_or_file_name.file_name() if isinstance(view_or_file_name, sublime.View) else view_or_file_name return self.includes_path(file_path) if file_path else False - def get_workspace_folders(self) -> List[WorkspaceFolder]: + def get_workspace_folders(self) -> list[WorkspaceFolder]: return [WorkspaceFolder.from_path(f) for f in self.folders] -def sorted_workspace_folders(folders: List[str], file_path: str) -> List[WorkspaceFolder]: - matching_paths: List[str] = [] - other_paths: List[str] = [] +def sorted_workspace_folders(folders: list[str], file_path: str) -> list[WorkspaceFolder]: + matching_paths: list[str] = [] + other_paths: list[str] = [] for folder in folders: is_subpath = is_subpath_of(file_path, folder) @@ -147,7 +147,7 @@ def enable_in_project(window: sublime.Window, config_name: str) -> None: window.set_project_data(project_data) else: sublime.message_dialog( - "Can't enable {} in the current workspace. Ensure that the project is saved first.".format(config_name)) + f"Can't enable {config_name} in the current workspace. Ensure that the project is saved first.") def disable_in_project(window: sublime.Window, config_name: str) -> None: @@ -160,4 +160,4 @@ def disable_in_project(window: sublime.Window, config_name: str) -> None: window.set_project_data(project_data) else: sublime.message_dialog( - "Can't disable {} in the current workspace. Ensure that the project is saved first.".format(config_name)) + f"Can't disable {config_name} in the current workspace. Ensure that the project is saved first.") diff --git a/plugin/diagnostics.py b/plugin/diagnostics.py index 6b5a011c4..22f426b37 100644 --- a/plugin/diagnostics.py +++ b/plugin/diagnostics.py @@ -6,11 +6,10 @@ from .core.settings import userprefs from .core.views import diagnostic_severity from .core.views import format_diagnostics_for_annotation -from typing import List, Tuple import sublime -class DiagnosticsAnnotationsView(): +class DiagnosticsAnnotationsView: def __init__(self, view: sublime.View, config_name: str) -> None: self._view = view @@ -22,16 +21,16 @@ def initialize_region_keys(self) -> None: self._view.add_regions(self._annotation_region_key(severity), r, flags=REGIONS_INITIALIZE_FLAGS) def _annotation_region_key(self, severity: DiagnosticSeverity) -> str: - return 'lsp_da-{}-{}'.format(severity, self._config_name) + return f'lsp_da-{severity}-{self._config_name}' - def draw(self, diagnostics: List[Tuple[Diagnostic, sublime.Region]]) -> None: + def draw(self, diagnostics: list[tuple[Diagnostic, sublime.Region]]) -> None: flags = sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.NO_UNDO max_severity_level = userprefs().show_diagnostics_annotations_severity_level # To achieve the correct order of annotations (most severe having priority) we have to add regions from the # most to the least severe. for severity in DIAGNOSTIC_KINDS.keys(): if severity <= max_severity_level: - matching_diagnostics: Tuple[List[Diagnostic], List[sublime.Region]] = ([], []) + matching_diagnostics: tuple[list[Diagnostic], list[sublime.Region]] = ([], []) for diagnostic, region in diagnostics: if diagnostic_severity(diagnostic) != severity: continue diff --git a/plugin/document_link.py b/plugin/document_link.py index 38423e5fb..5cc6a951a 100644 --- a/plugin/document_link.py +++ b/plugin/document_link.py @@ -7,14 +7,13 @@ from .core.protocol import URI from .core.registry import get_position from .core.registry import LspTextCommand -from typing import Optional import sublime class LspOpenLinkCommand(LspTextCommand): capability = 'documentLinkProvider' - def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None) -> bool: + def is_enabled(self, event: dict | None = None, point: int | None = None) -> bool: if not super().is_enabled(event, point): return False position = get_position(self.view, event) @@ -29,7 +28,7 @@ def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None) link = sv.session_buffer.get_document_link_at_point(self.view, position) return link is not None - def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None: + def run(self, edit: sublime.Edit, event: dict | None = None) -> None: point = get_position(self.view, event) if not point: return diff --git a/plugin/documents.py b/plugin/documents.py index 6b814ab4d..901d81fda 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -55,7 +55,7 @@ from .session_buffer import SessionBuffer from .session_view import SessionView from functools import partial -from typing import Any, Callable, Dict, Generator, Iterable, List, Optional, Tuple +from typing import Any, Callable, Generator, Iterable from typing import cast from weakref import WeakSet from weakref import WeakValueDictionary @@ -133,7 +133,7 @@ def on_revert_async(self) -> None: listener.revert_async() def __repr__(self) -> str: - return "TextChangeListener({})".format(self.buffer.buffer_id) + return f"TextChangeListener({self.buffer.buffer_id})" class DocumentSyncListener(sublime_plugin.ViewEventListener, AbstractViewListener): @@ -169,8 +169,8 @@ def on_change() -> None: self._change_count_on_last_save = -1 self._code_lenses_debouncer_async = DebouncerNonThreadSafe(async_thread=True) self._registration = SettingsRegistration(view.settings(), on_change=on_change) - self._completions_task: Optional[QueryCompletionsTask] = None - self._stored_selection: List[sublime.Region] = [] + self._completions_task: QueryCompletionsTask | None = None + self._stored_selection: list[sublime.Region] = [] self._should_format_on_paste = False self.hover_provider_count = 0 self._setup() @@ -185,18 +185,18 @@ def _setup(self) -> None: else: debug("view", self.view.id(), "has no syntax") self._language_id = "" - self._manager: Optional[WindowManager] = None - self._session_views: Dict[str, SessionView] = {} + self._manager: WindowManager | None = None + self._session_views: dict[str, SessionView] = {} self._stored_selection = [] - self._sighelp: Optional[SigHelp] = None - self._lightbulb_line: Optional[int] = None - self._diagnostics_for_selection: List[Tuple[SessionBufferProtocol, List[Diagnostic]]] = [] - self._code_actions_for_selection: List[CodeActionsByConfigName] = [] + self._sighelp: SigHelp | None = None + self._lightbulb_line: int | None = None + self._diagnostics_for_selection: list[tuple[SessionBufferProtocol, list[Diagnostic]]] = [] + self._code_actions_for_selection: list[CodeActionsByConfigName] = [] self._registered = False def _cleanup(self) -> None: settings = self.view.settings() - triggers: List[Dict[str, str]] = settings.get("auto_complete_triggers") or [] + triggers: list[dict[str, str]] = settings.get("auto_complete_triggers") or [] triggers = [trigger for trigger in triggers if 'server' not in trigger] settings.set("auto_complete_triggers", triggers) self._stored_selection = [] @@ -252,7 +252,7 @@ def on_session_shutdown_async(self, session: Session) -> None: def _diagnostics_async( self, allow_stale: bool = False - ) -> Generator[Tuple[SessionBufferProtocol, List[Tuple[Diagnostic, sublime.Region]]], None, None]: + ) -> Generator[tuple[SessionBufferProtocol, list[tuple[Diagnostic, sublime.Region]]], None, None]: change_count = self.view.change_count() for sb in self.session_buffers_async(): if sb.diagnostics_version == change_count or allow_stale: @@ -261,11 +261,11 @@ def _diagnostics_async( def diagnostics_intersecting_region_async( self, region: sublime.Region - ) -> Tuple[List[Tuple[SessionBufferProtocol, List[Diagnostic]]], sublime.Region]: + ) -> tuple[list[tuple[SessionBufferProtocol, list[Diagnostic]]], sublime.Region]: covering = sublime.Region(region.begin(), region.end()) - result: List[Tuple[SessionBufferProtocol, List[Diagnostic]]] = [] + result: list[tuple[SessionBufferProtocol, list[Diagnostic]]] = [] for sb, diagnostics in self._diagnostics_async(): - intersections: List[Diagnostic] = [] + intersections: list[Diagnostic] = [] for diagnostic, candidate in diagnostics: # Checking against points is inclusive unlike checking whether region intersects another region # which is exclusive (at region end) and we want an inclusive behavior in this case. @@ -280,11 +280,11 @@ def diagnostics_touching_point_async( self, pt: int, max_diagnostic_severity_level: int = DiagnosticSeverity.Hint - ) -> Tuple[List[Tuple[SessionBufferProtocol, List[Diagnostic]]], sublime.Region]: + ) -> tuple[list[tuple[SessionBufferProtocol, list[Diagnostic]]], sublime.Region]: covering = sublime.Region(pt, pt) - result: List[Tuple[SessionBufferProtocol, List[Diagnostic]]] = [] + result: list[tuple[SessionBufferProtocol, list[Diagnostic]]] = [] for sb, diagnostics in self._diagnostics_async(): - intersections: List[Diagnostic] = [] + intersections: list[Diagnostic] = [] for diagnostic, candidate in diagnostics: severity = diagnostic_severity(diagnostic) if severity > max_diagnostic_severity_level: @@ -319,7 +319,7 @@ def _update_diagnostic_in_status_bar_async(self) -> None: return self.view.erase_status(self.ACTIVE_DIAGNOSTIC) - def session_buffers_async(self, capability: Optional[str] = None) -> Generator[SessionBuffer, None, None]: + def session_buffers_async(self, capability: str | None = None) -> Generator[SessionBuffer, None, None]: for sv in self.session_views_async(): if capability is None or sv.has_capability_async(capability): yield sv.session_buffer @@ -444,7 +444,7 @@ def on_close(self) -> None: sublime.set_timeout_async(lambda: manager.unregister_listener_async(self)) self._clear_session_views_async() - def on_query_context(self, key: str, operator: int, operand: Any, match_all: bool) -> Optional[bool]: + def on_query_context(self, key: str, operator: int, operand: Any, match_all: bool) -> bool | None: # You can filter key bindings by the precense of a provider, if key == "lsp.session_with_capability" and operator == sublime.OP_EQUAL and isinstance(operand, str): capabilities = [s.strip() for s in operand.split("|")] @@ -493,8 +493,8 @@ def _on_hover_gutter_async(self, point: int) -> None: if self._lightbulb_line == self.view.rowcol(point)[0]: content += code_actions_content(self._code_actions_for_selection) if userprefs().show_diagnostics_severity_level: - diagnostics_with_config: List[Tuple[ClientConfig, Diagnostic]] = [] - diagnostics_by_session_buffer: List[Tuple[SessionBufferProtocol, List[Diagnostic]]] = [] + diagnostics_with_config: list[tuple[ClientConfig, Diagnostic]] = [] + diagnostics_by_session_buffer: list[tuple[SessionBufferProtocol, list[Diagnostic]]] = [] max_severity_level = min(userprefs().show_diagnostics_severity_level, DiagnosticSeverity.Information) if userprefs().diagnostics_gutter_marker: diagnostics_by_session_buffer = self.diagnostics_intersecting_async(self.view.line(point))[0] @@ -521,7 +521,7 @@ def _on_hover_gutter_async(self, point: int) -> None: location=point, on_navigate=lambda href: self._on_navigate(href, point)) - def on_text_command(self, command_name: str, args: Optional[dict]) -> Optional[Tuple[str, dict]]: + def on_text_command(self, command_name: str, args: dict | None) -> tuple[str, dict] | None: if command_name == "auto_complete": self._auto_complete_triggered_manually = True elif command_name == "show_scope_name" and userprefs().semantic_highlighting: @@ -536,7 +536,7 @@ def on_text_command(self, command_name: str, args: Optional[dict]) -> Optional[T return ('paste', {}) return None - def on_post_text_command(self, command_name: str, args: Optional[Dict[str, Any]]) -> None: + def on_post_text_command(self, command_name: str, args: dict[str, Any] | None) -> None: if command_name == 'paste': format_on_paste = self.view.settings().get('lsp_format_on_paste', userprefs().lsp_format_on_paste) if format_on_paste and self.session_async("documentRangeFormattingProvider"): @@ -549,7 +549,7 @@ def on_post_text_command(self, command_name: str, args: Optional[Dict[str, Any]] # hide the popup when `esc` or arrows are pressed pressed self.view.hide_popup() - def on_query_completions(self, prefix: str, locations: List[int]) -> Optional[sublime.CompletionList]: + def on_query_completions(self, prefix: str, locations: list[int]) -> sublime.CompletionList | None: completion_list = sublime.CompletionList() triggered_manually = self._auto_complete_triggered_manually self._auto_complete_triggered_manually = False # reset state for next completion popup @@ -574,7 +574,7 @@ def _on_query_completions_async( self._completions_task.query_completions_async(sessions) def _on_query_completions_resolved_async( - self, clist: sublime.CompletionList, completions: List[sublime.CompletionItem], flags: int = 0 + self, clist: sublime.CompletionList, completions: list[sublime.CompletionItem], flags: int = 0 ) -> None: self._completions_task = None # Resolve on the main thread to prevent any sort of data race for _set_target (see sublime_plugin.py). @@ -587,7 +587,7 @@ def do_signature_help_async(self, manual: bool) -> None: if not session or not self._stored_selection: return pos = self._stored_selection[0].a - triggers: List[str] = [] + triggers: list[str] = [] if not manual: for sb in self.session_buffers_async(): if session == sb.session: @@ -628,7 +628,7 @@ def do_signature_help_async(self, manual: bool) -> None: self.view.hide_popup() self._sighelp = None - def _get_signature_help_session(self) -> Optional[Session]: + def _get_signature_help_session(self) -> Session | None: # NOTE: We take the beginning of the region to check the previous char (see last_char variable). This is for # when a language server inserts a snippet completion. if not self._stored_selection: @@ -638,9 +638,9 @@ def _get_signature_help_session(self) -> Optional[Session]: def _on_signature_help( self, - response: Optional[SignatureHelp], + response: SignatureHelp | None, point: int, - language_map: Optional[MarkdownLangMap] + language_map: MarkdownLangMap | None ) -> None: self._sighelp = SigHelp.from_lsp(response, language_map) if self._sighelp: @@ -691,7 +691,7 @@ def _do_code_actions_async(self) -> None: .request_for_region_async(self.view, covering, self._diagnostics_for_selection, manual=False) \ .then(self._on_code_actions) - def _on_code_actions(self, responses: List[CodeActionsByConfigName]) -> None: + def _on_code_actions(self, responses: list[CodeActionsByConfigName]) -> None: self._code_actions_for_selection = responses action_count = 0 first_action_title = '' @@ -716,9 +716,9 @@ def _on_code_actions(self, responses: List[CodeActionsByConfigName]) -> None: icon = 'Packages/LSP/icons/lightbulb.png' self._lightbulb_line = self.view.rowcol(regions[0].begin())[0] else: # 'annotation' - title = '{} code actions'.format(action_count) if action_count > 1 else first_action_title + title = f'{action_count} code actions' if action_count > 1 else first_action_title code_actions_link = make_link('code-actions:', title) - annotations = ["
{}
".format(code_actions_link)] + annotations = [f"
{code_actions_link}
"] annotation_color = self.view.style_for_scope("region.bluish markup.accent.codeaction.lsp")["foreground"] self.view.add_regions( SessionView.CODE_ACTIONS_KEY, regions, scope, icon, flags, annotations, annotation_color, @@ -752,7 +752,7 @@ def _on_navigate(self, href: str, point: int) -> None: else: open_in_browser(href) - def handle_code_action_select(self, config_name: str, actions: List[CodeActionOrCommand], index: int) -> None: + def handle_code_action_select(self, config_name: str, actions: list[CodeActionOrCommand], index: int) -> None: if index == -1: return @@ -813,10 +813,10 @@ def _do_highlights_async(self) -> None: request = Request.documentHighlight(params, self.view) session.send_request_async(request, self._on_highlights) - def _on_highlights(self, response: Optional[List[DocumentHighlight]]) -> None: + def _on_highlights(self, response: list[DocumentHighlight] | None) -> None: if not isinstance(response, list): response = [] - kind2regions: Dict[Tuple[DocumentHighlightKind, bool], List[sublime.Region]] = {} + kind2regions: dict[tuple[DocumentHighlightKind, bool], list[sublime.Region]] = {} for highlight in response: r = range_to_region(highlight["range"], self.view) multiline = len(self.view.split_by_newlines(r)) > 1 @@ -841,7 +841,7 @@ def render_highlights_on_main_thread() -> None: # --- textDocument/foldingRange ------------------------------------------------------------------------------------ - def _on_initial_folding_ranges(self, kinds: List[str], response: Optional[List[FoldingRange]]) -> None: + def _on_initial_folding_ranges(self, kinds: list[str], response: list[FoldingRange] | None) -> None: if not response: return regions = [ @@ -854,21 +854,21 @@ def _on_initial_folding_ranges(self, kinds: List[str], response: Optional[List[F # --- Public utility methods --------------------------------------------------------------------------------------- - def session_async(self, capability: str, point: Optional[int] = None) -> Optional[Session]: + def session_async(self, capability: str, point: int | None = None) -> Session | None: return best_session(self.view, self.sessions_async(capability), point) - def sessions_async(self, capability: Optional[str] = None) -> Generator[Session, None, None]: + def sessions_async(self, capability: str | None = None) -> Generator[Session, None, None]: for sb in self.session_buffers_async(): if capability is None or sb.has_capability(capability): yield sb.session - def session_by_name(self, name: Optional[str] = None) -> Optional[Session]: + def session_by_name(self, name: str | None = None) -> Session | None: for sb in self.session_buffers_async(): if sb.session.config.name == name: return sb.session return None - def get_capability_async(self, session: Session, capability_path: str) -> Optional[Any]: + def get_capability_async(self, session: Session, capability_path: str) -> Any | None: for sv in self.session_views_async(): if sv.session == session: return sv.get_capability_async(capability_path) @@ -953,7 +953,7 @@ def _on_view_updated_async(self) -> None: self._do_highlights_async, first_region, after_ms=self.highlights_debounce_time) self.do_signature_help_async(manual=False) - def _update_stored_selection_async(self) -> Tuple[Optional[sublime.Region], bool]: + def _update_stored_selection_async(self) -> tuple[sublime.Region | None, bool]: """ Stores the current selection in a variable. Note that due to this function (supposedly) running in the async worker thread of ST, it can happen that the @@ -981,7 +981,7 @@ def _format_on_paste_async(self) -> None: split_clipboard_text = clipboard_text.split('\n') multi_cursor_paste = len(split_clipboard_text) == len(sel) and len(sel) > 1 original_selection = list(sel) - regions_to_format: List[sublime.Region] = [] + regions_to_format: list[sublime.Region] = [] pasted_text = clipboard_text # add regions to selection, in order for lsp_format_document_range to format those regions for index, region in enumerate(sel): @@ -1030,4 +1030,4 @@ def _on_settings_object_changed(self) -> None: self._reset() def __repr__(self) -> str: - return "ViewListener({})".format(self.view.id()) + return f"ViewListener({self.view.id()})" diff --git a/plugin/edit.py b/plugin/edit.py index fecf999d0..4f711b5dc 100644 --- a/plugin/edit.py +++ b/plugin/edit.py @@ -5,7 +5,7 @@ from .core.protocol import WorkspaceEdit from .core.registry import LspWindowCommand from contextlib import contextmanager -from typing import Any, Generator, Iterable, List, Optional, Tuple +from typing import Any, Generator, Iterable, Tuple import operator import re import sublime @@ -44,8 +44,8 @@ class LspApplyDocumentEditCommand(sublime_plugin.TextCommand): def run( self, edit: sublime.Edit, - changes: List[TextEdit], - required_view_version: Optional[int] = None, + changes: list[TextEdit], + required_view_version: int | None = None, process_placeholders: bool = False, ) -> None: # Apply the changes in reverse, so that we don't invalidate the range @@ -61,7 +61,7 @@ def run( last_row, _ = self.view.rowcol_utf16(self.view.size()) placeholder_region_count = 0 for start, end, replacement in reversed(_sort_by_application_order(edits)): - placeholder_region: Optional[Tuple[Tuple[int, int], Tuple[int, int]]] = None + placeholder_region: tuple[tuple[int, int], tuple[int, int]] | None = None if process_placeholders and replacement: parsed = self.parse_snippet(replacement) if parsed: @@ -108,7 +108,7 @@ def apply_change(self, region: sublime.Region, replacement: str, edit: sublime.E else: self.view.erase(edit, region) - def parse_snippet(self, replacement: str) -> Optional[Tuple[str, Tuple[int, int]]]: + def parse_snippet(self, replacement: str) -> tuple[str, tuple[int, int]] | None: match = re.search(self.re_placeholder, replacement) if not match: return @@ -127,7 +127,7 @@ def _parse_text_edit(text_edit: TextEdit) -> TextEditTuple: ) -def _sort_by_application_order(changes: Iterable[TextEditTuple]) -> List[TextEditTuple]: +def _sort_by_application_order(changes: Iterable[TextEditTuple]) -> list[TextEditTuple]: # The spec reads: # > However, it is possible that multiple edits have the same start position: multiple # > inserts, or any number of inserts followed by a single remove or replace edit. If diff --git a/plugin/execute_command.py b/plugin/execute_command.py index 62f0619c3..84e47212b 100644 --- a/plugin/execute_command.py +++ b/plugin/execute_command.py @@ -8,7 +8,7 @@ from .core.views import text_document_identifier from .core.views import text_document_position_params from .core.views import uri_from_view -from typing import Any, List, Optional +from typing import Any import sublime @@ -19,10 +19,10 @@ class LspExecuteCommand(LspTextCommand): def run(self, edit: sublime.Edit, - command_name: Optional[str] = None, - command_args: Optional[List[Any]] = None, - session_name: Optional[str] = None, - event: Optional[dict] = None) -> None: + command_name: str | None = None, + command_args: list[Any] | None = None, + session_name: str | None = None, + event: dict | None = None) -> None: session = self.session_by_name(session_name if session_name else self.session_name) if session and command_name: params: ExecuteCommandParams = {"command": command_name} @@ -45,7 +45,7 @@ def handle_success_async(self, result: Any, command_name: str) -> None: :param result: The result returned from the server. :param command_name: The name of the command that was executed. """ - msg = "command {} completed".format(command_name) + msg = f"command {command_name} completed" window = self.view.window() if window: window.status_message(msg) @@ -57,9 +57,9 @@ def handle_error_async(self, error: Error, command_name: str) -> None: :param error: The Error object. :param command_name: The name of the command that was executed. """ - sublime.message_dialog("command {} failed. Reason: {}".format(command_name, str(error))) + sublime.message_dialog(f"command {command_name} failed. Reason: {str(error)}") - def _expand_variables(self, command_args: List[Any]) -> List[Any]: + def _expand_variables(self, command_args: list[Any]) -> list[Any]: view = self.view region = first_selection_region(view) for i, arg in enumerate(command_args): diff --git a/plugin/folding_range.py b/plugin/folding_range.py index 436b3cba5..edf6c4782 100644 --- a/plugin/folding_range.py +++ b/plugin/folding_range.py @@ -9,7 +9,6 @@ from .core.views import range_to_region from .core.views import text_document_identifier from functools import partial -from typing import List, Optional import sublime @@ -26,7 +25,7 @@ def folding_range_to_range(folding_range: FoldingRange) -> Range: } -def sorted_folding_ranges(folding_ranges: List[FoldingRange]) -> List[FoldingRange]: +def sorted_folding_ranges(folding_ranges: list[FoldingRange]) -> list[FoldingRange]: # Sort by reversed position and from innermost to outermost (if nested) return sorted( folding_ranges, @@ -55,17 +54,17 @@ class LspFoldCommand(LspTextCommand): """ capability = 'foldingRangeProvider' - folding_ranges: List[FoldingRange] = [] + folding_ranges: list[FoldingRange] = [] change_count = -1 - folding_region: Optional[sublime.Region] = None + folding_region: sublime.Region | None = None def is_visible( self, prefetch: bool = False, hidden: bool = False, strict: bool = True, - event: Optional[dict] = None, - point: Optional[int] = None + event: dict | None = None, + point: int | None = None ) -> bool: if not prefetch: return True @@ -91,7 +90,7 @@ def is_visible( return False return self.folding_region is not None # Already set or unset by self.description - def _handle_response_async(self, change_count: int, response: Optional[List[FoldingRange]]) -> None: + def _handle_response_async(self, change_count: int, response: list[FoldingRange] | None) -> None: self.change_count = change_count self.folding_ranges = response or [] @@ -100,8 +99,8 @@ def description( prefetch: bool = False, hidden: bool = False, strict: bool = True, - event: Optional[dict] = None, - point: Optional[int] = None + event: dict | None = None, + point: int | None = None ) -> str: if not prefetch: return "LSP: Fold" @@ -128,7 +127,7 @@ def description( if kind == FoldingRangeKind.Imports: return "LSP: Fold Imports" elif kind: - return "LSP: Fold this {}".format(kind.title()) + return f"LSP: Fold this {kind.title()}" else: return "LSP: Fold" return "LSP " # is_visible will return False @@ -139,8 +138,8 @@ def run( prefetch: bool = False, hidden: bool = False, strict: bool = True, - event: Optional[dict] = None, - point: Optional[int] = None + event: dict | None = None, + point: int | None = None ) -> None: if prefetch: if self.folding_region is not None: @@ -162,7 +161,7 @@ def run( partial(self._handle_response_manual_async, pt, strict) ) - def _handle_response_manual_async(self, point: int, strict: bool, response: Optional[List[FoldingRange]]) -> None: + def _handle_response_manual_async(self, point: int, strict: bool, response: list[FoldingRange] | None) -> None: if not response: window = self.view.window() if window: @@ -185,14 +184,14 @@ class LspFoldAllCommand(LspTextCommand): capability = 'foldingRangeProvider' - def run(self, edit: sublime.Edit, kind: Optional[str] = None, event: Optional[dict] = None) -> None: + def run(self, edit: sublime.Edit, kind: str | None = None, event: dict | None = None) -> None: session = self.best_session(self.capability) if session: params: FoldingRangeParams = {'textDocument': text_document_identifier(self.view)} session.send_request_async( Request.foldingRange(params, self.view), partial(self._handle_response_async, kind)) - def _handle_response_async(self, kind: Optional[str], response: Optional[List[FoldingRange]]) -> None: + def _handle_response_async(self, kind: str | None, response: list[FoldingRange] | None) -> None: if not response: return regions = [ diff --git a/plugin/formatting.py b/plugin/formatting.py index 1cb44ceea..ea06c7be5 100644 --- a/plugin/formatting.py +++ b/plugin/formatting.py @@ -18,23 +18,23 @@ from .core.views import will_save_wait_until from .save_command import LspSaveCommand, SaveTask from functools import partial -from typing import Callable, Iterator, List, Optional, Union +from typing import Callable, Iterator, List, Union import sublime FormatResponse = Union[List[TextEdit], None, Error] -def get_formatter(window: Optional[sublime.Window], base_scope: str) -> Optional[str]: +def get_formatter(window: sublime.Window | None, base_scope: str) -> str | None: window_manager = windows.lookup(window) if not window_manager: return None project_data = window_manager.window.project_data() - return DottedDict(project_data).get('settings.LSP.formatters.{}'.format(base_scope)) if \ + return DottedDict(project_data).get(f'settings.LSP.formatters.{base_scope}') if \ isinstance(project_data, dict) else window_manager.formatters.get(base_scope) -def format_document(text_command: LspTextCommand, formatter: Optional[str] = None) -> Promise[FormatResponse]: +def format_document(text_command: LspTextCommand, formatter: str | None = None) -> Promise[FormatResponse]: view = text_command.view if formatter: session = text_command.session_by_name(formatter, LspFormatDocumentCommand.capability) @@ -58,7 +58,7 @@ def is_applicable(cls, view: sublime.View) -> bool: def __init__(self, task_runner: LspTextCommand, on_complete: Callable[[], None]) -> None: super().__init__(task_runner, on_complete) - self._session_iterator: Optional[Iterator[Session]] = None + self._session_iterator: Iterator[Session] | None = None def run_async(self) -> None: super().run_async() @@ -114,12 +114,12 @@ class LspFormatDocumentCommand(LspTextCommand): capability = 'documentFormattingProvider' - def is_enabled(self, event: Optional[dict] = None, select: bool = False) -> bool: + def is_enabled(self, event: dict | None = None, select: bool = False) -> bool: if select: return len(list(self.sessions(self.capability))) > 1 return super().is_enabled() or bool(self.best_session(LspFormatDocumentRangeCommand.capability)) - def run(self, edit: sublime.Edit, event: Optional[dict] = None, select: bool = False) -> None: + def run(self, edit: sublime.Edit, event: dict | None = None, select: bool = False) -> None: session_names = [session.config.name for session in self.sessions(self.capability)] base_scope = self.view.syntax().scope if select: @@ -139,14 +139,14 @@ def on_result(self, result: FormatResponse) -> None: if result and not isinstance(result, Error): apply_text_edits(self.view, result) - def select_formatter(self, base_scope: str, session_names: List[str]) -> None: + def select_formatter(self, base_scope: str, session_names: list[str]) -> None: window = self.view.window() if not window: return window.show_quick_panel( session_names, partial(self.on_select_formatter, base_scope, session_names), placeholder="Select Formatter") - def on_select_formatter(self, base_scope: str, session_names: List[str], index: int) -> None: + def on_select_formatter(self, base_scope: str, session_names: list[str], index: int) -> None: if index == -1: return session_name = session_names[index] @@ -172,7 +172,7 @@ class LspFormatDocumentRangeCommand(LspTextCommand): capability = 'documentRangeFormattingProvider' - def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None) -> bool: + def is_enabled(self, event: dict | None = None, point: int | None = None) -> bool: if not super().is_enabled(event, point): return False if has_single_nonempty_selection(self.view): @@ -182,7 +182,7 @@ def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None) return True return False - def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None: + def run(self, edit: sublime.Edit, event: dict | None = None) -> None: if has_single_nonempty_selection(self.view): session = self.best_session(self.capability) selection = first_selection_region(self.view) @@ -198,13 +198,13 @@ def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None: class LspFormatCommand(LspTextCommand): - def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None) -> bool: + def is_enabled(self, event: dict | None = None, point: int | None = None) -> bool: if not super().is_enabled(): return False return bool(self.best_session('documentFormattingProvider')) or \ bool(self.best_session('documentRangeFormattingProvider')) - def is_visible(self, event: Optional[dict] = None, point: Optional[int] = None) -> bool: + def is_visible(self, event: dict | None = None, point: int | None = None) -> bool: return self.is_enabled(event, point) def description(self, **kwargs) -> str: @@ -214,7 +214,7 @@ def description(self, **kwargs) -> str: return "Format Selections" return "Format File" - def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None: + def run(self, edit: sublime.Edit, event: dict | None = None) -> None: command = 'lsp_format_document_range' if self._range_formatting_available() else 'lsp_format_document' self.view.run_command(command) diff --git a/plugin/goto.py b/plugin/goto.py index 8e6421865..104b5873f 100644 --- a/plugin/goto.py +++ b/plugin/goto.py @@ -10,7 +10,6 @@ from .locationpicker import LocationPicker from .locationpicker import open_location_async from functools import partial -from typing import List, Optional, Union import sublime @@ -22,8 +21,8 @@ class LspGotoCommand(LspTextCommand): def is_enabled( self, - event: Optional[dict] = None, - point: Optional[int] = None, + event: dict | None = None, + point: int | None = None, side_by_side: bool = False, force_group: bool = True, fallback: bool = False, @@ -33,8 +32,8 @@ def is_enabled( def is_visible( self, - event: Optional[dict] = None, - point: Optional[int] = None, + event: dict | None = None, + point: int | None = None, side_by_side: bool = False, force_group: bool = True, fallback: bool = False, @@ -47,8 +46,8 @@ def is_visible( def run( self, _: sublime.Edit, - event: Optional[dict] = None, - point: Optional[int] = None, + event: dict | None = None, + point: int | None = None, side_by_side: bool = False, force_group: bool = True, fallback: bool = False, @@ -74,7 +73,7 @@ def _handle_response_async( fallback: bool, group: int, position: int, - response: Union[None, Location, List[Location], List[LocationLink]] + response: None | Location | list[Location] | list[LocationLink] ) -> None: if isinstance(response, dict): self.view.run_command("add_jump_record", {"selection": [(r.a, r.b) for r in self.view.sel()]}) diff --git a/plugin/goto_diagnostic.py b/plugin/goto_diagnostic.py index da6f344b5..d1c706836 100644 --- a/plugin/goto_diagnostic.py +++ b/plugin/goto_diagnostic.py @@ -25,7 +25,7 @@ from .core.views import uri_from_view from collections import Counter, OrderedDict from pathlib import Path -from typing import Dict, Iterator, List, Optional, Tuple, Union +from typing import Iterator from typing import cast import functools import os @@ -50,10 +50,10 @@ def get_sessions(window: sublime.Window) -> Iterator[Session]: class LspGotoDiagnosticCommand(sublime_plugin.WindowCommand): - def run(self, uri: Optional[DocumentUri], diagnostic: Optional[dict]) -> None: # type: ignore + def run(self, uri: DocumentUri | None, diagnostic: dict | None) -> None: # type: ignore pass - def is_enabled(self, uri: Optional[DocumentUri] = None, diagnostic: Optional[dict] = None) -> bool: # type: ignore + def is_enabled(self, uri: DocumentUri | None = None, diagnostic: dict | None = None) -> bool: # type: ignore view = self.window.active_view() if view is None: return False @@ -73,7 +73,7 @@ def is_enabled(self, uri: Optional[DocumentUri] = None, diagnostic: Optional[dic for diagnostic in diagnostics if is_severity_included(max_severity)(diagnostic)) - def input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]: + def input(self, args: dict) -> sublime_plugin.CommandInputHandler | None: uri, diagnostic = args.get("uri"), args.get("diagnostic") view = self.window.active_view() if view is None: @@ -95,10 +95,10 @@ def input_description(self) -> str: class DiagnosticUriInputHandler(PreselectedListInputHandler): - _preview: Optional[sublime.View] = None - uri: Optional[DocumentUri] = None + _preview: sublime.View | None = None + uri: DocumentUri | None = None - def __init__(self, window: sublime.Window, view: sublime.View, initial_value: Optional[DocumentUri] = None) -> None: + def __init__(self, window: sublime.Window, view: sublime.View, initial_value: DocumentUri | None = None) -> None: super().__init__(window, initial_value) self.window = window self.view = view @@ -106,11 +106,11 @@ def __init__(self, window: sublime.Window, view: sublime.View, initial_value: Op def name(self) -> str: return "uri" - def get_list_items(self) -> Tuple[List[sublime.ListInputItem], int]: + def get_list_items(self) -> tuple[list[sublime.ListInputItem], int]: max_severity = userprefs().diagnostics_panel_include_severity_level # collect severities and location of first diagnostic per uri - severities_per_path: OrderedDict[ParsedUri, List[DiagnosticSeverity]] = OrderedDict() - self.first_locations: Dict[ParsedUri, Tuple[Session, Location]] = dict() + severities_per_path: OrderedDict[ParsedUri, list[DiagnosticSeverity]] = OrderedDict() + self.first_locations: dict[ParsedUri, tuple[Session, Location]] = dict() for session in get_sessions(self.window): for parsed_uri, severity in session.diagnostics.filter_map_diagnostics_flat_async( is_severity_included(max_severity), lambda _, diagnostic: diagnostic_severity(diagnostic)): @@ -125,9 +125,8 @@ def get_list_items(self) -> Tuple[List[sublime.ListInputItem], int]: selected = 0 for i, (parsed_uri, severities) in enumerate(severities_per_path.items()): counts = Counter(severities) - text = "{}: {}".format(format_severity(min(counts)), self._simple_project_path(parsed_uri)) - annotation = "E: {}, W: {}".format(counts.get(DiagnosticSeverity.Error, 0), - counts.get(DiagnosticSeverity.Warning, 0)) + text = f"{format_severity(min(counts))}: {self._simple_project_path(parsed_uri)}" + annotation = f"E: {counts.get(DiagnosticSeverity.Error, 0)}, W: {counts.get(DiagnosticSeverity.Warning, 0)}" kind = DIAGNOSTIC_KINDS[min(counts)] uri = unparse_uri(parsed_uri) if uri == self.uri: @@ -138,7 +137,7 @@ def get_list_items(self) -> Tuple[List[sublime.ListInputItem], int]: def placeholder(self) -> str: return "Select file" - def next_input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]: + def next_input(self, args: dict) -> sublime_plugin.CommandInputHandler | None: uri, diagnostic = args.get("uri"), args.get("diagnostic") if uri is None: return None @@ -147,7 +146,7 @@ def next_input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler] return DiagnosticInputHandler(self.window, self.view, uri) return sublime_plugin.BackInputHandler() - def confirm(self, value: Optional[DocumentUri]) -> None: + def confirm(self, value: DocumentUri | None) -> None: self.uri = value def description(self, value: DocumentUri, text: str) -> str: @@ -160,7 +159,7 @@ def cancel(self) -> None: self._preview.close() self.window.focus_view(self.view) - def preview(self, value: Optional[DocumentUri]) -> str: + def preview(self, value: DocumentUri | None) -> str: if not value or not hasattr(self, 'first_locations'): return "" parsed_uri = parse_uri(value) @@ -185,7 +184,7 @@ def _project_path(self, parsed_uri: ParsedUri) -> str: class DiagnosticInputHandler(sublime_plugin.ListInputHandler): - _preview: Optional[sublime.View] = None + _preview: sublime.View | None = None def __init__(self, window: sublime.Window, view: sublime.View, uri: DocumentUri) -> None: self.window = window @@ -196,8 +195,8 @@ def __init__(self, window: sublime.Window, view: sublime.View, uri: DocumentUri) def name(self) -> str: return "diagnostic" - def list_items(self) -> List[sublime.ListInputItem]: - list_items: List[sublime.ListInputItem] = [] + def list_items(self) -> list[sublime.ListInputItem]: + list_items: list[sublime.ListInputItem] = [] max_severity = userprefs().diagnostics_panel_include_severity_level for i, session in enumerate(self.sessions): for diagnostic in filter(is_severity_included(max_severity), @@ -206,7 +205,7 @@ def list_items(self) -> List[sublime.ListInputItem]: first_line = lines[0] if lines else "" if len(lines) > 1: first_line += " …" - text = "{}: {}".format(format_severity(diagnostic_severity(diagnostic)), first_line) + text = f"{format_severity(diagnostic_severity(diagnostic))}: {first_line}" annotation = format_diagnostic_source_and_code(diagnostic) kind = DIAGNOSTIC_KINDS[diagnostic_severity(diagnostic)] list_items.append(sublime.ListInputItem(text, [i, diagnostic], annotation=annotation, kind=kind)) @@ -215,10 +214,10 @@ def list_items(self) -> List[sublime.ListInputItem]: def placeholder(self) -> str: return "Select diagnostic" - def next_input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]: + def next_input(self, args: dict) -> sublime_plugin.CommandInputHandler | None: return None if args.get("diagnostic") else sublime_plugin.BackInputHandler() # type: ignore - def confirm(self, value: Optional[list]) -> None: + def confirm(self, value: list | None) -> None: if not value: return i = cast(int, value[0]) @@ -238,7 +237,7 @@ def cancel(self) -> None: self._preview.close() self.window.focus_view(self.view) - def preview(self, value: Optional[list]) -> Union[str, sublime.Html]: + def preview(self, value: list | None) -> str | sublime.Html: if not value: return "" i = cast(int, value[0]) @@ -268,7 +267,7 @@ def open_location(session: Session, location: Location, flags: int = 0, group: i return session.window.open_file(file_name, flags=flags | sublime.ENCODED_POSITION, group=group) -def diagnostic_html(config: ClientConfig, diagnostic: Diagnostic, base_dir: Optional[Path]) -> sublime.Html: +def diagnostic_html(config: ClientConfig, diagnostic: Diagnostic, base_dir: Path | None) -> sublime.Html: content = format_diagnostic_for_html( config, truncate_message(diagnostic), None if base_dir is None else str(base_dir)) return sublime.Html('
{}
'.format( diff --git a/plugin/hierarchy.py b/plugin/hierarchy.py index b8ec69abe..c426462a9 100644 --- a/plugin/hierarchy.py +++ b/plugin/hierarchy.py @@ -24,7 +24,7 @@ from abc import ABCMeta from abc import abstractmethod from functools import partial -from typing import Callable, List, Optional, TypedDict, Union +from typing import Callable, TypedDict, Union from typing import cast import sublime import weakref @@ -44,8 +44,8 @@ def __init__( self, weaksession: weakref.ref[Session], request: Callable[..., Request], - request_handler: Callable[..., List[HierarchyItemWrapper]], - root_elements: List[HierarchyItemWrapper] + request_handler: Callable[..., list[HierarchyItemWrapper]], + root_elements: list[HierarchyItemWrapper] ) -> None: self.weaksession = weaksession self.request = request @@ -54,7 +54,7 @@ def __init__( session = self.weaksession() self.session_name = session.config.name if session else None - def get_children(self, element: Optional[HierarchyItemWrapper]) -> Promise[List[HierarchyItemWrapper]]: + def get_children(self, element: HierarchyItemWrapper | None) -> Promise[list[HierarchyItemWrapper]]: if element is None: return Promise.resolve(self.root_elements) session = self.weaksession() @@ -85,7 +85,7 @@ def get_tree_item(self, element: HierarchyItemWrapper) -> TreeItem: def make_data_provider( - weaksession: weakref.ref[Session], sheet_name: str, direction: int, response: List[HierarchyItemWrapper] + weaksession: weakref.ref[Session], sheet_name: str, direction: int, response: list[HierarchyItemWrapper] ) -> HierarchyDataProvider: if sheet_name == "Call Hierarchy": request = Request.incomingCalls if direction == 1 else Request.outgoingCalls @@ -94,26 +94,26 @@ def make_data_provider( request = Request.supertypes if direction == 1 else Request.subtypes handler = type_hierarchy_handler else: - raise NotImplementedError('{} not implemented'.format(sheet_name)) + raise NotImplementedError(f'{sheet_name} not implemented') return HierarchyDataProvider(weaksession, request, handler, response) -def incoming_calls_handler(response: Union[List[CallHierarchyIncomingCall], None, Error]) -> List[HierarchyItemWrapper]: +def incoming_calls_handler(response: list[CallHierarchyIncomingCall] | None | Error) -> list[HierarchyItemWrapper]: return [ to_hierarchy_data(call['from'], call['fromRanges'][0] if call['fromRanges'] else None) for call in response ] if isinstance(response, list) else [] -def outgoing_calls_handler(response: Union[List[CallHierarchyOutgoingCall], None, Error]) -> List[HierarchyItemWrapper]: +def outgoing_calls_handler(response: list[CallHierarchyOutgoingCall] | None | Error) -> list[HierarchyItemWrapper]: return [to_hierarchy_data(call['to']) for call in response] if isinstance(response, list) else [] -def type_hierarchy_handler(response: Union[List[TypeHierarchyItem], None, Error]) -> List[HierarchyItemWrapper]: +def type_hierarchy_handler(response: list[TypeHierarchyItem] | None | Error) -> list[HierarchyItemWrapper]: return [to_hierarchy_data(item) for item in response] if isinstance(response, list) else [] def to_hierarchy_data( - item: Union[CallHierarchyItem, TypeHierarchyItem], selection_range: Optional[Range] = None + item: CallHierarchyItem | TypeHierarchyItem, selection_range: Range | None = None ) -> HierarchyItemWrapper: return { 'item': item, @@ -121,7 +121,7 @@ def to_hierarchy_data( } -def make_header(session_name: str, sheet_name: str, direction: int, root_elements: List[HierarchyItem]) -> str: +def make_header(session_name: str, sheet_name: str, direction: int, root_elements: list[HierarchyItem]) -> str: if sheet_name == "Call Hierarchy": label = "Callers of…" if direction == 1 else "Calls from…" tooltip = "Show Outgoing Calls" if direction == 1 else "Show Incoming Calls" @@ -129,7 +129,7 @@ def make_header(session_name: str, sheet_name: str, direction: int, root_element label = "Supertypes of…" if direction == 1 else "Subtypes of…" tooltip = "Show Subtypes" if direction == 1 else "Show Supertypes" else: - raise NotImplementedError('{} not implemented'.format(sheet_name)) + raise NotImplementedError(f'{sheet_name} not implemented') new_direction = 2 if direction == 1 else 1 return '{}: {} {}'.format(sheet_name, label, make_command_link('lsp_hierarchy_toggle', "⇄", { 'session_name': session_name, @@ -145,16 +145,16 @@ class LspHierarchyCommand(LspTextCommand, metaclass=ABCMeta): @abstractmethod def request( cls, params: TextDocumentPositionParams, view: sublime.View - ) -> Request[Union[List[HierarchyItem], Error, None]]: + ) -> Request[list[HierarchyItem] | Error | None]: """ A function that generates the initial request when this command is invoked. """ raise NotImplementedError() - def is_visible(self, event: Optional[dict] = None, point: Optional[int] = None) -> bool: + def is_visible(self, event: dict | None = None, point: int | None = None) -> bool: if self.applies_to_context_menu(event): return self.is_enabled(event, point) return True - def run(self, edit: sublime.Edit, event: Optional[dict] = None, point: Optional[int] = None) -> None: + def run(self, edit: sublime.Edit, event: dict | None = None, point: int | None = None) -> None: self._window = self.view.window() session = self.best_session(self.capability) if not session: @@ -167,7 +167,7 @@ def run(self, edit: sublime.Edit, event: Optional[dict] = None, point: Optional[ self.request(params, self.view), partial(self._handle_response_async, weakref.ref(session))) def _handle_response_async( - self, weaksession: weakref.ref[Session], response: Optional[List[HierarchyItem]] + self, weaksession: weakref.ref[Session], response: list[HierarchyItem] | None ) -> None: if not self._window or not self._window.is_valid(): return @@ -176,9 +176,9 @@ def _handle_response_async( elif self.capability == 'typeHierarchyProvider': sheet_name = "Type Hierarchy" else: - raise NotImplementedError('{} not implemented'.format(self.capability)) + raise NotImplementedError(f'{self.capability} not implemented') if not response: - self._window.status_message("{} not available".format(sheet_name)) + self._window.status_message(f"{sheet_name} not available") return session = weaksession() if not session: @@ -193,7 +193,7 @@ def _handle_response_async( class LspHierarchyToggleCommand(LspWindowCommand): def run( - self, session_name: str, sheet_name: str, direction: int, root_elements: List[HierarchyItemWrapper] + self, session_name: str, sheet_name: str, direction: int, root_elements: list[HierarchyItemWrapper] ) -> None: session = self.session_by_name(session_name) if not session: @@ -204,7 +204,7 @@ def run( open_first(self.window, session.config.name, root_elements) -def open_first(window: sublime.Window, session_name: str, items: List[HierarchyItemWrapper]) -> None: +def open_first(window: sublime.Window, session_name: str, items: list[HierarchyItemWrapper]) -> None: if items and window.is_valid(): item = items[0]['item'] window.run_command('lsp_open_location', { @@ -225,7 +225,7 @@ class LspCallHierarchyCommand(LspHierarchyCommand): @classmethod def request( cls, params: TextDocumentPositionParams, view: sublime.View - ) -> Request[Union[List[CallHierarchyItem], Error, None]]: + ) -> Request[list[CallHierarchyItem] | Error | None]: return Request.prepareCallHierarchy(cast(CallHierarchyPrepareParams, params), view) @@ -236,5 +236,5 @@ class LspTypeHierarchyCommand(LspHierarchyCommand): @classmethod def request( cls, params: TextDocumentPositionParams, view: sublime.View - ) -> Request[Union[List[TypeHierarchyItem], Error, None]]: + ) -> Request[list[TypeHierarchyItem] | Error | None]: return Request.prepareTypeHierarchy(cast(TypeHierarchyPrepareParams, params), view) diff --git a/plugin/hover.py b/plugin/hover.py index 2b647bd78..9466aa128 100644 --- a/plugin/hover.py +++ b/plugin/hover.py @@ -39,7 +39,7 @@ from .core.views import unpack_href_location from .core.views import update_lsp_popup from functools import partial -from typing import Dict, List, Optional, Sequence, Tuple, Union +from typing import Sequence, Union from urllib.parse import urlparse import html import mdpopups @@ -52,7 +52,7 @@ ResolvedHover = Union[Hover, Error] -_test_contents: List[str] = [] +_test_contents: list[str] = [] class LinkKind: @@ -84,20 +84,20 @@ def link(self, point: int, view: sublime.View) -> str: ] -def code_actions_content(actions_by_config: List[CodeActionsByConfigName]) -> str: +def code_actions_content(actions_by_config: list[CodeActionsByConfigName]) -> str: formatted = [] for config_name, actions in actions_by_config: action_count = len(actions) if action_count == 0: continue if action_count > 1: - text = "choose ({} available)".format(action_count) + text = f"choose ({action_count} available)" else: text = actions[0].get('title', 'code action') href = "{}:{}".format('code-actions', config_name) link = make_link(href, text) formatted.append( - '
Quick Fix: {} {}
'.format(link, config_name)) + f'
Quick Fix: {link} {config_name}
') return "".join(formatted) @@ -105,15 +105,15 @@ class LspHoverCommand(LspTextCommand): def __init__(self, view: sublime.View) -> None: super().__init__(view) - self._base_dir: Optional[str] = None + self._base_dir: str | None = None self._image_resolver = None def run( self, edit: sublime.Edit, only_diagnostics: bool = False, - point: Optional[int] = None, - event: Optional[dict] = None + point: int | None = None, + event: dict | None = None ) -> None: temp_point = point if temp_point is None: @@ -127,10 +127,10 @@ def run( return hover_point = temp_point self._base_dir = wm.get_project_path(self.view.file_name() or "") - self._hover_responses: List[Tuple[Hover, Optional[MarkdownLangMap]]] = [] - self._document_links: List[DocumentLink] = [] - self._actions_by_config: List[CodeActionsByConfigName] = [] - self._diagnostics_by_config: Sequence[Tuple[SessionBufferProtocol, Sequence[Diagnostic]]] = [] + self._hover_responses: list[tuple[Hover, MarkdownLangMap | None]] = [] + self._document_links: list[DocumentLink] = [] + self._actions_by_config: list[CodeActionsByConfigName] = [] + self._diagnostics_by_config: Sequence[tuple[SessionBufferProtocol, Sequence[Diagnostic]]] = [] # TODO: For code actions it makes more sense to use the whole selection under mouse (if available) # rather than just the hover point. @@ -154,8 +154,8 @@ def run_async() -> None: sublime.set_timeout_async(run_async) def request_symbol_hover_async(self, listener: AbstractViewListener, point: int) -> None: - hover_promises: List[Promise[ResolvedHover]] = [] - language_maps: List[Optional[MarkdownLangMap]] = [] + hover_promises: list[Promise[ResolvedHover]] = [] + language_maps: list[MarkdownLangMap | None] = [] for session in listener.sessions_async('hoverProvider'): hover_promises.append(session.send_request_task( Request("textDocument/hover", text_document_position_params(self.view, point), self.view) @@ -167,11 +167,11 @@ def _on_all_settled( self, listener: AbstractViewListener, point: int, - language_maps: List[Optional[MarkdownLangMap]], - responses: List[ResolvedHover] + language_maps: list[MarkdownLangMap | None], + responses: list[ResolvedHover] ) -> None: - hovers: List[Tuple[Hover, Optional[MarkdownLangMap]]] = [] - errors: List[Error] = [] + hovers: list[tuple[Hover, MarkdownLangMap | None]] = [] + errors: list[Error] = [] for response, language_map in zip(responses, language_maps): if isinstance(response, Error): errors.append(response) @@ -180,12 +180,12 @@ def _on_all_settled( hovers.append((response, language_map)) if errors: error_messages = ", ".join(str(error) for error in errors) - sublime.status_message('Hover error: {}'.format(error_messages)) + sublime.status_message(f'Hover error: {error_messages}') self._hover_responses = hovers self.show_hover(listener, point, only_diagnostics=False) def request_document_link_async(self, listener: AbstractViewListener, point: int) -> None: - link_promises: List[Promise[DocumentLink]] = [] + link_promises: list[Promise[DocumentLink]] = [] for sv in listener.session_views_async(): if not sv.has_capability_async("documentLinkProvider"): continue @@ -208,7 +208,7 @@ def _on_resolved_link(self, session_buffer: SessionBufferProtocol, link: Documen return link def _on_all_document_links_resolved( - self, listener: AbstractViewListener, point: int, links: List[DocumentLink] + self, listener: AbstractViewListener, point: int, links: list[DocumentLink] ) -> None: self._document_links = links self.show_hover(listener, point, only_diagnostics=False) @@ -217,19 +217,19 @@ def _handle_code_actions( self, listener: AbstractViewListener, point: int, - responses: List[CodeActionsByConfigName] + responses: list[CodeActionsByConfigName] ) -> None: self._actions_by_config = responses self.show_hover(listener, point, only_diagnostics=False) def provider_exists(self, listener: AbstractViewListener, link: LinkKind) -> bool: - return bool(listener.session_async('{}Provider'.format(link.lsp_name))) + return bool(listener.session_async(f'{link.lsp_name}Provider')) def symbol_actions_content(self, listener: AbstractViewListener, point: int) -> str: actions = [lk.link(point, self.view) for lk in link_kinds if self.provider_exists(listener, lk)] return " | ".join(actions) if actions else "" - def link_content_and_range(self) -> Tuple[str, Optional[sublime.Region]]: + def link_content_and_range(self) -> tuple[str, sublime.Region | None]: if len(self._document_links) > 1: combined_region = range_to_region(self._document_links[0]["range"], self.view) for link in self._document_links[1:]: @@ -243,16 +243,16 @@ def link_content_and_range(self) -> Tuple[str, Optional[sublime.Region]]: target = link.get("target") label = "Follow Link" if link.get("target", "file:").startswith("file:") else "Open in Browser" title = link.get("tooltip") - tooltip = ' title="{}"'.format(html.escape(title)) if title else "" + tooltip = f' title="{html.escape(title)}"' if title else "" region = range_to_region(link["range"], self.view) - return '{}'.format(html.escape(target), tooltip, label) if target else label, region + return f'{label}' if target else label, region else: return "", None def diagnostics_content(self) -> str: formatted = [] for sb, diagnostics in self._diagnostics_by_config: - by_severity: Dict[int, List[str]] = {} + by_severity: dict[int, list[str]] = {} formatted.append('
') for diagnostic in diagnostics: by_severity.setdefault(diagnostic_severity(diagnostic), []).append( @@ -270,7 +270,7 @@ def hover_content(self) -> str: contents.append(minihtml(self.view, content, allowed_formats, language_map)) return '
'.join(contents) - def hover_range(self) -> Optional[sublime.Region]: + def hover_range(self) -> sublime.Region | None: for hover, _ in self._hover_responses: hover_range = hover.get('range') if hover_range: @@ -357,7 +357,7 @@ def _on_navigate(self, href: str, point: int) -> None: if window: targets = [link["target"] for link in self._document_links] # pyright: ignore - def on_select(targets: List[str], idx: int) -> None: + def on_select(targets: list[str], idx: int) -> None: if idx > -1: self._on_navigate(targets[idx], 0) @@ -375,7 +375,7 @@ def on_select(targets: List[str], idx: int) -> None: else: sublime.set_timeout_async(partial(self.try_open_custom_uri_async, href)) - def handle_code_action_select(self, config_name: str, actions: List[CodeActionOrCommand], index: int) -> None: + def handle_code_action_select(self, config_name: str, actions: list[CodeActionOrCommand], index: int) -> None: if index == -1: return diff --git a/plugin/inlay_hint.py b/plugin/inlay_hint.py index c302d01b0..819e7a2b8 100644 --- a/plugin/inlay_hint.py +++ b/plugin/inlay_hint.py @@ -10,7 +10,6 @@ from .core.sessions import Session from .core.settings import userprefs from .core.views import position_to_offset -from typing import Optional, Union from typing import cast import html import sublime @@ -20,12 +19,12 @@ class LspToggleInlayHintsCommand(LspWindowCommand): capability = 'inlayHintProvider' - def run(self, enable: Optional[bool] = None) -> None: + def run(self, enable: bool | None = None) -> None: if not isinstance(enable, bool): enable = not self.are_enabled(self.window) self.window.settings().set('lsp_show_inlay_hints', enable) status = 'on' if enable else 'off' - sublime.status_message('Inlay Hints are {}'.format(status)) + sublime.status_message(f'Inlay Hints are {status}') for session in self.sessions(): for sv in session.session_views_async(): sv.session_buffer.do_inlay_hints_async(sv.view) @@ -34,7 +33,7 @@ def is_checked(self) -> bool: return self.are_enabled(self.window) @classmethod - def are_enabled(cls, window: Optional[sublime.Window]) -> bool: + def are_enabled(cls, window: sublime.Window | None) -> bool: if not window: return userprefs().show_inlay_hints return bool(window.settings().get('lsp_show_inlay_hints', userprefs().show_inlay_hints)) @@ -44,7 +43,7 @@ class LspInlayHintClickCommand(LspTextCommand): capability = 'inlayHintProvider' def run(self, _edit: sublime.Edit, session_name: str, inlay_hint: InlayHint, phantom_uuid: str, - event: Optional[dict] = None, label_part: Optional[InlayHintLabelPart] = None) -> None: + event: dict | None = None, label_part: InlayHintLabelPart | None = None) -> None: # Insert textEdits for the given inlay hint. # If a InlayHintLabelPart was clicked, label_part will be passed as an argument to the LspInlayHintClickCommand # and InlayHintLabelPart.command will be executed. @@ -58,7 +57,7 @@ def run(self, _edit: sublime.Edit, session_name: str, inlay_hint: InlayHint, pha self.handle(session_name, inlay_hint, phantom_uuid, label_part) def handle(self, session_name: str, inlay_hint: InlayHint, phantom_uuid: str, - label_part: Optional[InlayHintLabelPart] = None) -> None: + label_part: InlayHintLabelPart | None = None) -> None: self.handle_inlay_hint_text_edits(session_name, inlay_hint, phantom_uuid) self.handle_label_part_command(session_name, label_part) @@ -73,7 +72,7 @@ def handle_inlay_hint_text_edits(self, session_name: str, inlay_hint: InlayHint, sb.remove_inlay_hint_phantom(phantom_uuid) apply_text_edits(self.view, text_edits) - def handle_label_part_command(self, session_name: str, label_part: Optional[InlayHintLabelPart] = None) -> None: + def handle_label_part_command(self, session_name: str, label_part: InlayHintLabelPart | None = None) -> None: if not label_part: return command = label_part.get('command') @@ -100,27 +99,23 @@ def inlay_hint_to_phantom(view: sublime.View, inlay_hint: InlayHint, session: Se def get_inlay_hint_html(view: sublime.View, inlay_hint: InlayHint, session: Session, phantom_uuid: str) -> str: label = format_inlay_hint_label(inlay_hint, session, phantom_uuid) font = view.settings().get('font_face') or "monospace" - html = """ + html = f"""
{label}
- """.format( - font=font, - css=css().inlay_hints, - label=label - ) + """ return html -def format_inlay_hint_tooltip(tooltip: Optional[Union[str, MarkupContent]]) -> str: +def format_inlay_hint_tooltip(tooltip: str | MarkupContent | None) -> str: if isinstance(tooltip, str): return html.escape(tooltip) if isinstance(tooltip, dict): # MarkupContent @@ -145,12 +140,9 @@ def format_inlay_hint_label(inlay_hint: InlayHint, session: Session, phantom_uui 'phantom_uuid': phantom_uuid } }) - result += ''.format(command=inlay_hint_click_command) + result += f'' instruction_text = '\nDouble-click to insert' if has_text_edits else "" - result += '{value}'.format( - tooltip=(tooltip + instruction_text).strip(), - value=html.escape(label) - ) + result += f'{html.escape(label)}' if is_clickable: result += "" return result @@ -169,14 +161,11 @@ def format_inlay_hint_label(inlay_hint: InlayHint, session: Session, phantom_uui 'label_part': cast(dict, label_part) } }) - value += ''.format(command=inlay_hint_click_command) + value += f'' value += html.escape(label_part['value']) if has_command: value += "" # InlayHintLabelPart.location is not supported instruction_text = '\nDouble-click to execute' if has_command else "" - result += "{value}".format( - tooltip=(tooltip + instruction_text).strip(), - value=value - ) + result += f"{value}" return result diff --git a/plugin/locationpicker.py b/plugin/locationpicker.py index 7fff94d4c..80bcf865f 100644 --- a/plugin/locationpicker.py +++ b/plugin/locationpicker.py @@ -9,7 +9,6 @@ from .core.views import get_uri_and_position_from_location from .core.views import location_to_human_readable from .core.views import to_encoded_filename -from typing import List, Optional, Tuple, Union from urllib.request import url2pathname import functools import sublime @@ -18,7 +17,7 @@ def open_location_async( session: Session, - location: Union[Location, LocationLink], + location: Location | LocationLink, side_by_side: bool, force_group: bool, group: int = -1 @@ -29,7 +28,7 @@ def open_location_async( if side_by_side: flags |= sublime.ADD_TO_SELECTION | sublime.SEMI_TRANSIENT - def check_success_async(view: Optional[sublime.View]) -> None: + def check_success_async(view: sublime.View | None) -> None: if not view: sublime.error_message("Unable to open URI") @@ -41,8 +40,8 @@ def open_basic_file( uri: str, position: Position, flags: int = 0, - group: Optional[int] = None -) -> Optional[sublime.View]: + group: int | None = None +) -> sublime.View | None: if group is None: group = session.window.active_group() if uri.startswith("file:"): @@ -63,7 +62,7 @@ def __init__( self, view: sublime.View, session: Session, - locations: Union[List[Location], List[LocationLink]], + locations: list[Location] | list[LocationLink], side_by_side: bool, force_group: bool = True, group: int = -1, @@ -82,7 +81,7 @@ def __init__( self._force_group = force_group self._group = group self._items = locations - self._highlighted_view: Optional[sublime.View] = None + self._highlighted_view: sublime.View | None = None manager = session.manager() base_dir = manager.get_project_path(view.file_name() or "") if manager else None self._window.focus_group(group) @@ -100,7 +99,7 @@ def __init__( placeholder=placeholder ) - def _unpack(self, index: int) -> Tuple[Optional[Session], Union[Location, LocationLink], DocumentUri, Position]: + def _unpack(self, index: int) -> tuple[Session | None, Location | LocationLink, DocumentUri, Position]: location = self._items[index] uri, position = get_uri_and_position_from_location(location) return self._weaksession(), location, uri, position @@ -120,7 +119,7 @@ def _select_entry(self, index: int) -> None: if not self._side_by_side: view = open_basic_file(session, uri, position, flags) if not view: - self._window.status_message("Unable to open {}".format(uri)) + self._window.status_message(f"Unable to open {uri}") else: sublime.set_timeout_async( functools.partial( diff --git a/plugin/panels.py b/plugin/panels.py index 834f862fc..370ca9d07 100644 --- a/plugin/panels.py +++ b/plugin/panels.py @@ -4,7 +4,7 @@ from .core.registry import windows from contextlib import contextmanager from sublime_plugin import WindowCommand -from typing import Generator, Optional +from typing import Generator import sublime import sublime_plugin @@ -50,12 +50,12 @@ def run(self) -> None: class LspToggleLogPanelLinesLimitCommand(sublime_plugin.TextCommand): @classmethod - def is_limit_enabled(cls, window: Optional[sublime.Window]) -> bool: + def is_limit_enabled(cls, window: sublime.Window | None) -> bool: wm = windows.lookup(window) return bool(wm and wm.is_log_lines_limit_enabled()) @classmethod - def get_lines_limit(cls, window: Optional[sublime.Window]) -> int: + def get_lines_limit(cls, window: sublime.Window | None) -> int: wm = windows.lookup(window) return wm.get_log_lines_limit() if wm else 0 @@ -86,7 +86,7 @@ class LspUpdatePanelCommand(sublime_plugin.TextCommand): A update_panel command to update the error panel with new text. """ - def run(self, edit: sublime.Edit, characters: Optional[str] = "") -> None: + def run(self, edit: sublime.Edit, characters: str | None = "") -> None: # Clear folds self.view.unfold(sublime.Region(0, self.view.size())) @@ -109,7 +109,7 @@ def run(self, edit: sublime.Edit) -> None: new_lines = [] for prefix, message in wm.get_and_clear_server_log(): message = message.replace("\r\n", "\n") # normalize Windows eol - new_lines.append("{}: {}\n".format(prefix, message)) + new_lines.append(f"{prefix}: {message}\n") if new_lines: self.view.insert(edit, self.view.size(), ''.join(new_lines)) last_region_end = 0 # Starting from point 0 in the panel ... diff --git a/plugin/references.py b/plugin/references.py index 6590b9114..ff94a6753 100644 --- a/plugin/references.py +++ b/plugin/references.py @@ -14,7 +14,7 @@ from .core.views import position_to_offset from .core.views import text_document_position_params from .locationpicker import LocationPicker -from typing import Dict, List, Literal, Optional, Tuple +from typing import Literal import functools import linecache import os @@ -30,27 +30,27 @@ class LspSymbolReferencesCommand(LspTextCommand): def is_enabled( self, - event: Optional[dict] = None, - point: Optional[int] = None, + event: dict | None = None, + point: int | None = None, side_by_side: bool = False, force_group: bool = True, fallback: bool = False, group: int = -1, include_declaration: bool = False, - output_mode: Optional[OutputMode] = None, + output_mode: OutputMode | None = None, ) -> bool: return fallback or super().is_enabled(event, point) def is_visible( self, - event: Optional[dict] = None, - point: Optional[int] = None, + event: dict | None = None, + point: int | None = None, side_by_side: bool = False, force_group: bool = True, fallback: bool = False, group: int = -1, include_declaration: bool = False, - output_mode: Optional[OutputMode] = None, + output_mode: OutputMode | None = None, ) -> bool: # We include "output panel" and "quick panel" variants of `LSP: Find References` in the Command Palette # but we only show the one that is not the same as the default one (per the `show_references_in_quick_panel` @@ -66,14 +66,14 @@ def is_visible( def run( self, _: sublime.Edit, - event: Optional[dict] = None, - point: Optional[int] = None, + event: dict | None = None, + point: int | None = None, side_by_side: bool = False, force_group: bool = True, fallback: bool = False, group: int = -1, include_declaration: bool = False, - output_mode: Optional[OutputMode] = None, + output_mode: OutputMode | None = None, ) -> None: session = self.best_session(self.capability) file_path = self.view.file_name() @@ -115,10 +115,10 @@ def _handle_response_async( force_group: bool, fallback: bool, group: int, - output_mode: Optional[OutputMode], - event: Optional[dict], + output_mode: OutputMode | None, + event: dict | None, position: int, - response: Optional[List[Location]] + response: list[Location] | None ) -> None: sublime.set_timeout(lambda: self._handle_response( word, session, side_by_side, force_group, fallback, group, output_mode, event, position, response)) @@ -131,10 +131,10 @@ def _handle_response( force_group: bool, fallback: bool, group: int, - output_mode: Optional[OutputMode], - event: Optional[dict], + output_mode: OutputMode | None, + event: dict | None, position: int, - response: Optional[List[Location]] + response: list[Location] | None ) -> None: if not response: self._handle_no_results(fallback, side_by_side) @@ -166,7 +166,7 @@ def _show_references_in_quick_panel( self, word: str, session: Session, - locations: List[Location], + locations: list[Location], side_by_side: bool, force_group: bool, group: int, @@ -189,7 +189,7 @@ def _show_references_in_quick_panel( break LocationPicker(self.view, session, locations, side_by_side, force_group, group, placeholder, kind, index) - def _show_references_in_output_panel(self, word: str, session: Session, locations: List[Location]) -> None: + def _show_references_in_output_panel(self, word: str, session: Session, locations: list[Location]) -> None: wm = windows.lookup(session.window) if not wm: return @@ -197,31 +197,31 @@ def _show_references_in_output_panel(self, word: str, session: Session, location if not panel: return base_dir = wm.get_project_path(self.view.file_name() or "") - to_render: List[str] = [] + to_render: list[str] = [] references_count = 0 references_by_file = _group_locations_by_uri(wm.window, session.config, locations) for file, references in references_by_file.items(): - to_render.append('{}:'.format(_get_relative_path(base_dir, file))) + to_render.append(f'{_get_relative_path(base_dir, file)}:') for reference in references: references_count += 1 point, line = reference - to_render.append(" {:>4}:{:<4} {}".format(point.row + 1, point.col + 1, line)) + to_render.append(f" {point.row + 1:>4}:{point.col + 1:<4} {line}") to_render.append("") # add spacing between filenames characters = "\n".join(to_render) panel.settings().set("result_base_dir", base_dir) panel.run_command("lsp_clear_panel") wm.window.run_command("show_panel", {"panel": "output.references"}) panel.run_command('append', { - 'characters': "{} references for '{}'\n\n{}".format(references_count, word, characters), + 'characters': f"{references_count} references for '{word}'\n\n{characters}", 'force': True, 'scroll_to_end': False }) # highlight all word occurrences - regions = panel.find_all(r"\b{}\b".format(word)) + regions = panel.find_all(rf"\b{word}\b") panel.add_regions('ReferenceHighlight', regions, 'comment', flags=sublime.DRAW_NO_FILL | sublime.NO_UNDO) -def _get_relative_path(base_dir: Optional[str], file_path: str) -> str: +def _get_relative_path(base_dir: str | None, file_path: str) -> str: if base_dir: try: return os.path.relpath(file_path, base_dir) @@ -234,10 +234,10 @@ def _get_relative_path(base_dir: Optional[str], file_path: str) -> str: def _group_locations_by_uri( window: sublime.Window, config: ClientConfig, - locations: List[Location] -) -> Dict[str, List[Tuple[Point, str]]]: + locations: list[Location] +) -> dict[str, list[tuple[Point, 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[Point, str]]] = {} for location in locations: uri, position = get_uri_and_position_from_location(location) file_path = config.map_server_uri_to_client_path(uri) diff --git a/plugin/rename.py b/plugin/rename.py index 1ab50322b..73a8d08d9 100644 --- a/plugin/rename.py +++ b/plugin/rename.py @@ -18,7 +18,7 @@ from .core.views import range_to_region from .core.views import text_document_position_params from functools import partial -from typing import Any, List, Optional +from typing import Any from typing import cast from typing_extensions import TypeGuard import os @@ -122,14 +122,14 @@ def is_visible( self, new_name: str = "", placeholder: str = "", - event: Optional[dict] = None, - point: Optional[int] = None + event: dict | None = None, + point: int | None = None ) -> bool: if self.applies_to_context_menu(event): return self.is_enabled(event, point) return True - def input(self, args: dict) -> Optional[sublime_plugin.TextInputHandler]: + def input(self, args: dict) -> sublime_plugin.TextInputHandler | None: if "new_name" in args: # Defer to "run" and trigger rename. return None @@ -154,8 +154,8 @@ def run( edit: sublime.Edit, new_name: str = "", placeholder: str = "", - event: Optional[dict] = None, - point: Optional[int] = None + event: dict | None = None, + point: int | None = None ) -> None: listener = self.get_listener() if listener: @@ -188,7 +188,7 @@ def _do_rename(self, position: int, new_name: str) -> None: request = Request.rename(params, self.view, progress=True) session.send_request(request, partial(self._on_rename_result_async, session)) - def _on_rename_result_async(self, session: Session, response: Optional[WorkspaceEdit]) -> None: + def _on_rename_result_async(self, session: Session, response: WorkspaceEdit | None) -> None: if not response: return session.window.status_message('Nothing to rename') changes = parse_workspace_edit(response) @@ -197,14 +197,14 @@ def _on_rename_result_async(self, session: Session, response: Optional[Workspace session.apply_parsed_workspace_edits(changes) return total_changes = sum(map(len, changes.values())) - message = "Replace {} occurrences across {} files?".format(total_changes, file_count) + message = f"Replace {total_changes} occurrences across {file_count} files?" choice = sublime.yes_no_cancel_dialog(message, "Replace", "Preview", title="Rename") if choice == sublime.DIALOG_YES: session.apply_parsed_workspace_edits(changes) elif choice == sublime.DIALOG_NO: self._render_rename_panel(response, changes, total_changes, file_count, session.config.name) - def _on_prepare_result(self, pos: int, response: Optional[PrepareRenameResult]) -> None: + def _on_prepare_result(self, pos: int, response: PrepareRenameResult | None) -> None: if response is None: sublime.error_message("The current selection cannot be renamed") return @@ -247,9 +247,9 @@ def _render_rename_panel( panel = pm.ensure_rename_panel() if not panel: return - to_render: List[str] = [] - reference_document: List[str] = [] - header_lines = "{} changes across {} files.\n".format(total_changes, file_count) + to_render: list[str] = [] + reference_document: list[str] = [] + header_lines = f"{total_changes} changes across {file_count} files.\n" to_render.append(header_lines) reference_document.append(header_lines) ROWCOL_PREFIX = " {:>4}:{:<4} {}" diff --git a/plugin/save_command.py b/plugin/save_command.py index 7833f0f7a..1fa95b93e 100644 --- a/plugin/save_command.py +++ b/plugin/save_command.py @@ -3,7 +3,7 @@ from .core.settings import userprefs from abc import ABCMeta, abstractmethod from functools import partial -from typing import Any, Callable, Dict, List, Optional, Type +from typing import Any, Callable import sublime import sublime_plugin @@ -33,7 +33,7 @@ def run_async(self) -> None: def _on_timeout(self) -> None: if not self._completed and not self._cancelled: - self._set_view_status('LSP: Timeout processing {}'.format(self.__class__.__name__)) + self._set_view_status(f'LSP: Timeout processing {self.__class__.__name__}') self._cancelled = True self._on_done() @@ -64,12 +64,12 @@ def _purge_changes_async(self) -> None: class SaveTasksRunner: def __init__( - self, text_command: LspTextCommand, tasks: List[Type[SaveTask]], on_complete: Callable[[], None] + self, text_command: LspTextCommand, tasks: list[type[SaveTask]], on_complete: Callable[[], None] ) -> None: self._text_command = text_command self._tasks = tasks self._on_tasks_completed = on_complete - self._pending_tasks: List[SaveTask] = [] + self._pending_tasks: list[SaveTask] = [] self._canceled = False def run(self) -> None: @@ -108,18 +108,18 @@ class LspSaveCommand(LspTextCommand): A command used as a substitute for native save command. Runs code actions and document formatting before triggering the native save command. """ - _tasks: List[Type[SaveTask]] = [] + _tasks: list[type[SaveTask]] = [] @classmethod - def register_task(cls, task: Type[SaveTask]) -> None: + def register_task(cls, task: type[SaveTask]) -> None: assert task not in cls._tasks cls._tasks.append(task) def __init__(self, view: sublime.View) -> None: super().__init__(view) - self._save_tasks_runner: Optional[SaveTasksRunner] = None + self._save_tasks_runner: SaveTasksRunner | None = None - def run(self, edit: sublime.Edit, **kwargs: Dict[str, Any]) -> None: + def run(self, edit: sublime.Edit, **kwargs: dict[str, Any]) -> None: if self._save_tasks_runner: self._save_tasks_runner.cancel() sublime.set_timeout_async(self._trigger_on_pre_save_async) @@ -134,7 +134,7 @@ def _trigger_on_pre_save_async(self) -> None: listener.trigger_on_pre_save_async() # type: ignore break - def _on_tasks_completed(self, kwargs: Dict[str, Any]) -> None: + def _on_tasks_completed(self, kwargs: dict[str, Any]) -> None: self._save_tasks_runner = None # Triggered from set_timeout to preserve original semantics of on_pre_save handling sublime.set_timeout(lambda: self.view.run_command('save', kwargs)) diff --git a/plugin/selection_range.py b/plugin/selection_range.py index 9d00171b3..911f08a04 100644 --- a/plugin/selection_range.py +++ b/plugin/selection_range.py @@ -5,7 +5,7 @@ from .core.registry import LspTextCommand from .core.views import range_to_region from .core.views import selection_range_params -from typing import Any, List, Optional, Tuple +from typing import Any import sublime @@ -15,18 +15,18 @@ class LspExpandSelectionCommand(LspTextCommand): def __init__(self, view: sublime.View) -> None: super().__init__(view) - self._regions: List[sublime.Region] = [] + self._regions: list[sublime.Region] = [] self._change_count = 0 - def is_enabled(self, event: Optional[dict] = None, point: Optional[int] = None, fallback: bool = False) -> bool: + def is_enabled(self, event: dict | None = None, point: int | None = None, fallback: bool = False) -> bool: return fallback or super().is_enabled(event, point) - def is_visible(self, event: Optional[dict] = None, point: Optional[int] = None, fallback: bool = False) -> bool: + def is_visible(self, event: dict | None = None, point: int | None = None, fallback: bool = False) -> bool: if self.applies_to_context_menu(event): return self.is_enabled(event, point, fallback) return True - def run(self, edit: sublime.Edit, event: Optional[dict] = None, fallback: bool = False) -> None: + def run(self, edit: sublime.Edit, event: dict | None = None, fallback: bool = False) -> None: position = get_position(self.view, event) if position is None: return @@ -37,9 +37,9 @@ def run(self, edit: sublime.Edit, event: Optional[dict] = None, fallback: bool = self._change_count = self.view.change_count() session.send_request(Request.selectionRange(params), self.on_result, self.on_error) elif fallback: - self._run_builtin_expand_selection("No {} found".format(self.capability)) + self._run_builtin_expand_selection(f"No {self.capability} found") - def on_result(self, params: Optional[List[SelectionRange]]) -> None: + def on_result(self, params: list[SelectionRange] | None) -> None: if self._change_count != self.view.change_count(): return if params: @@ -59,10 +59,10 @@ def _status_message(self, msg: str) -> None: window.status_message(msg) def _run_builtin_expand_selection(self, fallback_reason: str) -> None: - self._status_message("{}, reverting to built-in Expand Selection".format(fallback_reason)) + self._status_message(f"{fallback_reason}, reverting to built-in Expand Selection") self.view.run_command("expand_selection", {"to": "smart"}) - def _smallest_containing(self, region: sublime.Region, param: SelectionRange) -> Tuple[int, int]: + def _smallest_containing(self, region: sublime.Region, param: SelectionRange) -> tuple[int, int]: r = range_to_region(param["range"], self.view) # Test for *strict* containment if r.contains(region) and (r.a < region.a or r.b > region.b): diff --git a/plugin/semantic_highlighting.py b/plugin/semantic_highlighting.py index 6aa0a31b8..c5cdcbbfb 100644 --- a/plugin/semantic_highlighting.py +++ b/plugin/semantic_highlighting.py @@ -1,6 +1,6 @@ from __future__ import annotations from .core.registry import LspTextCommand -from typing import Any, List, Tuple +from typing import Any, List from typing import cast import sublime import os @@ -10,7 +10,7 @@ class SemanticToken: __slots__ = ("region", "type", "modifiers") - def __init__(self, region: sublime.Region, type: str, modifiers: List[str]): + def __init__(self, region: sublime.Region, type: str, modifiers: list[str]): self.region = region self.type = type self.modifiers = modifiers @@ -53,7 +53,7 @@ def run(self, _: sublime.Edit) -> None: token_modifiers ) - def _get_semantic_info(self, point: int) -> Tuple[str, str]: + def _get_semantic_info(self, point: int) -> tuple[str, str]: session = self.best_session('semanticTokensProvider') session_buffer = None if session: @@ -76,7 +76,7 @@ def _render_with_plain_string_stackframes( self, scope: str, scope_list: str, - stack: List[str], + stack: list[str], token_type: str, token_modifiers: str ) -> None: @@ -93,7 +93,7 @@ def _render_with_plain_string_stackframes( if backtrace: backtrace += '\n' - backtrace += '
%s%s
' % (nums, ctx) + backtrace += f'
{nums}{ctx}
' html = """ @@ -139,7 +139,7 @@ def _render_with_fancy_stackframes( self, scope: str, scope_list: str, - stack: List[Any], + stack: list[Any], token_type: str, token_modifiers: str ) -> None: @@ -168,11 +168,11 @@ def _render_with_fancy_stackframes( else: href = resource_path location = display_path - link = '%s' % (href, location) + link = f'{location}' if backtrace: backtrace += '\n' - backtrace += '
%s%s%s
' % (nums, ctx, link) + backtrace += f'
{nums}{ctx}{link}
' html = """ diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index aab9c46ed..d8eb80df7 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -46,7 +46,7 @@ from .inlay_hint import LspToggleInlayHintsCommand from .semantic_highlighting import SemanticToken from functools import partial -from typing import Any, Callable, Dict, Iterable, List, Optional, Protocol, Set, Tuple, Union +from typing import Any, Callable, Iterable, List, Protocol from typing import cast from typing_extensions import TypeGuard from weakref import WeakSet @@ -87,13 +87,13 @@ class SemanticTokensData: 'data', 'result_id', 'active_region_keys', 'tokens', 'view_change_count', 'needs_refresh', 'pending_response') def __init__(self) -> None: - self.data: List[int] = [] - self.result_id: Optional[str] = None - self.active_region_keys: Set[int] = set() - self.tokens: List[SemanticToken] = [] + self.data: list[int] = [] + self.result_id: str | None = None + self.active_region_keys: set[int] = set() + self.tokens: list[SemanticToken] = [] self.view_change_count = 0 self.needs_refresh = False - self.pending_response: Optional[int] = None + self.pending_response: int | None = None class SessionBuffer: @@ -115,21 +115,21 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self._session_views.add(session_view) self._last_known_uri = uri self._id = buffer_id - self._pending_changes: Optional[PendingChanges] = None - self.diagnostics: List[Tuple[Diagnostic, sublime.Region]] = [] + self._pending_changes: PendingChanges | None = None + self.diagnostics: list[tuple[Diagnostic, sublime.Region]] = [] self.diagnostics_version = -1 self.diagnostics_flags = 0 self._diagnostics_are_visible = False self.document_diagnostic_needs_refresh = False - self._document_diagnostic_pending_response: Optional[int] = None + self._document_diagnostic_pending_response: int | None = None self._last_synced_version = 0 self._last_text_change_time = 0.0 self._diagnostics_debouncer_async = DebouncerNonThreadSafe(async_thread=True) self._workspace_diagnostics_debouncer_async = DebouncerNonThreadSafe(async_thread=True) self._color_phantoms = sublime.PhantomSet(view, "lsp_color") - self._document_links: List[DocumentLink] = [] + self._document_links: list[DocumentLink] = [] self.semantic_tokens = SemanticTokensData() - self._semantic_region_keys: Dict[str, int] = {} + self._semantic_region_keys: dict[str, int] = {} self._last_semantic_region_key = 0 self._inlay_hints_phantom_set = sublime.PhantomSet(view, "lsp_inlay_hints") self.inlay_hints_needs_refresh = False @@ -146,7 +146,7 @@ def session_views(self) -> WeakSet[SessionViewProtocol]: return self._session_views @property - def version(self) -> Optional[int]: + def version(self) -> int | None: view = self.some_view() return view.change_count() if view else None @@ -174,12 +174,12 @@ def _check_did_close(self, view: sublime.View) -> None: self.session.send_notification(did_close(uri=self._last_known_uri)) self.opened = False - def get_uri(self) -> Optional[DocumentUri]: + def get_uri(self) -> DocumentUri | None: for sv in self.session_views: return sv.get_uri() return None - def get_language_id(self) -> Optional[str]: + def get_language_id(self) -> str | None: for sv in self.session_views: return sv.get_language_id() return None @@ -227,10 +227,10 @@ def register_capability_async( registration_id: str, capability_path: str, registration_path: str, - options: Dict[str, Any] + options: dict[str, Any] ) -> None: self.capabilities.register(registration_id, capability_path, registration_path, options) - view: Optional[sublime.View] = None + view: sublime.View | None = None for sv in self.session_views: sv.on_capability_added_async(registration_id, capability_path, options) if view is None: @@ -253,7 +253,7 @@ def unregister_capability_async( for sv in self.session_views: sv.on_capability_removed_async(registration_id, discarded) - def get_capability(self, capability_path: str) -> Optional[Any]: + def get_capability(self, capability_path: str) -> Any | None: if self.session.config.is_disabled_capability(capability_path): return None value = self.capabilities.get(capability_path) @@ -273,7 +273,7 @@ def should_notify_did_open(self) -> bool: def should_notify_will_save(self) -> bool: return self.capabilities.should_notify_will_save() or self.session.should_notify_will_save() - def should_notify_did_save(self) -> Tuple[bool, bool]: + def should_notify_did_save(self) -> tuple[bool, bool]: do_it, include_text = self.capabilities.should_notify_did_save() return (do_it, include_text) if do_it else self.session.should_notify_did_save() @@ -377,7 +377,7 @@ def on_post_save_async(self, view: sublime.View, new_uri: DocumentUri) -> None: self._has_changed_during_save = False self._on_after_change_async(view, view.change_count()) - def some_view(self) -> Optional[sublime.View]: + def some_view(self) -> sublime.View | None: if not self.session_views: return None # Prefer active view if possible @@ -408,7 +408,7 @@ def _do_color_boxes_async(self, view: sublime.View, version: int) -> None: self._if_view_unchanged(self._on_color_boxes_async, version) ) - def _on_color_boxes_async(self, view: sublime.View, response: List[ColorInformation]) -> None: + def _on_color_boxes_async(self, view: sublime.View, response: list[ColorInformation]) -> None: if response is None: # Guard against spec violation from certain language servers self._color_phantoms.update([]) return @@ -424,7 +424,7 @@ def _do_document_link_async(self, view: sublime.View, version: int) -> None: self._if_view_unchanged(self._on_document_link_async, version) ) - def _on_document_link_async(self, view: sublime.View, response: Optional[List[DocumentLink]]) -> None: + def _on_document_link_async(self, view: sublime.View, response: list[DocumentLink] | None) -> None: self._document_links = response or [] if self._document_links and userprefs().link_highlight_style == "underline": view.add_regions( @@ -435,7 +435,7 @@ def _on_document_link_async(self, view: sublime.View, response: Optional[List[Do else: view.erase_regions("lsp_document_link") - def get_document_link_at_point(self, view: sublime.View, point: int) -> Optional[DocumentLink]: + def get_document_link_at_point(self, view: sublime.View, point: int) -> DocumentLink | None: for link in self._document_links: if range_to_region(link["range"], view).contains(point): return link @@ -452,7 +452,7 @@ def update_document_link(self, new_link: DocumentLink) -> None: # --- textDocument/diagnostic -------------------------------------------------------------------------------------- - def do_document_diagnostic_async(self, view: sublime.View, version: Optional[int] = None) -> None: + def do_document_diagnostic_async(self, view: sublime.View, version: int | None = None) -> None: mgr = self.session.manager() if not mgr: return @@ -481,7 +481,7 @@ def _on_document_diagnostic_async(self, version: int, response: DocumentDiagnost self._if_view_unchanged(self._apply_document_diagnostic_async, version)(response) def _apply_document_diagnostic_async( - self, view: Optional[sublime.View], response: DocumentDiagnosticReport + self, view: sublime.View | None, response: DocumentDiagnosticReport ) -> None: self.session.diagnostics_result_ids[self._last_known_uri] = response.get('resultId') if is_full_document_diagnostic_report(response): @@ -509,7 +509,7 @@ def set_document_diagnostic_pending_refresh(self, needs_refresh: bool = True) -> # --- textDocument/publishDiagnostics ------------------------------------------------------------------------------ def on_diagnostics_async( - self, raw_diagnostics: List[Diagnostic], version: Optional[int], visible_session_views: Set[SessionViewProtocol] + self, raw_diagnostics: list[Diagnostic], version: int | None, visible_session_views: set[SessionViewProtocol] ) -> None: view = self.some_view() if view is None: @@ -520,8 +520,8 @@ def on_diagnostics_async( if version != change_count: return diagnostics_version = version - diagnostics: List[Tuple[Diagnostic, sublime.Region]] = [] - data_per_severity: Dict[Tuple[int, bool], DiagnosticSeverityData] = {} + diagnostics: list[tuple[Diagnostic, sublime.Region]] = [] + data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] = {} for diagnostic in raw_diagnostics: region = range_to_region(diagnostic["range"], view) severity = diagnostic_severity(diagnostic) @@ -576,7 +576,7 @@ def do_semantic_tokens_async(self, view: sublime.View, only_viewport: bool = Fal if self.semantic_tokens.pending_response: self.session.cancel_request(self.semantic_tokens.pending_response) self.semantic_tokens.view_change_count = view.change_count() - params: Dict[str, Any] = {"textDocument": text_document_identifier(view)} + params: dict[str, Any] = {"textDocument": text_document_identifier(view)} if only_viewport and self.has_capability("semanticTokensProvider.range"): params["range"] = region_to_range(view, view.visible_region()) request = Request.semanticTokensRange(cast(SemanticTokensRangeParams, params), view) @@ -597,18 +597,18 @@ def do_semantic_tokens_async(self, view: sublime.View, only_viewport: bool = Fal self.semantic_tokens.pending_response = self.session.send_request_async( request, self._on_semantic_tokens_async, self._on_semantic_tokens_error_async) - def _on_semantic_tokens_async(self, response: Optional[Dict]) -> None: + def _on_semantic_tokens_async(self, response: dict | None) -> None: self.semantic_tokens.pending_response = None if response: self.semantic_tokens.result_id = response.get("resultId") self.semantic_tokens.data = response["data"] self._draw_semantic_tokens_async() - def _on_semantic_tokens_viewport_async(self, view: sublime.View, response: Optional[Dict]) -> None: + def _on_semantic_tokens_viewport_async(self, view: sublime.View, response: dict | None) -> None: self._on_semantic_tokens_async(response) self.do_semantic_tokens_async(view) # now request semantic tokens for the full file - def _on_semantic_tokens_delta_async(self, response: Optional[Dict]) -> None: + def _on_semantic_tokens_delta_async(self, response: dict | None) -> None: self.semantic_tokens.pending_response = None if response: self.semantic_tokens.result_id = response.get("resultId") @@ -635,7 +635,7 @@ def _draw_semantic_tokens_async(self) -> None: if view is None: return self.semantic_tokens.tokens.clear() - scope_regions: Dict[int, Tuple[str, List[sublime.Region]]] = dict() + scope_regions: dict[int, tuple[str, list[sublime.Region]]] = dict() prev_line = 0 prev_col_utf16 = 0 types_legend = tuple(cast(List[str], self.get_capability('semanticTokensProvider.legend.tokenTypes'))) @@ -662,13 +662,13 @@ def _draw_semantic_tokens_async(self) -> None: # customizations in the color scheme for the scopes of custom token types would require a restart of # Sublime Text to take effect. token_general_style = view.style_for_scope("meta.semantic-token") - token_type_style = view.style_for_scope("meta.semantic-token.{}".format(token_type.lower())) + token_type_style = view.style_for_scope(f"meta.semantic-token.{token_type.lower()}") if token_general_style["source_line"] != token_type_style["source_line"] or \ token_general_style["source_column"] != token_type_style["source_column"]: if token_modifiers: - scope = "meta.semantic-token.{}.{}.lsp".format(token_type.lower(), token_modifiers[0].lower()) + scope = f"meta.semantic-token.{token_type.lower()}.{token_modifiers[0].lower()}.lsp" else: - scope = "meta.semantic-token.{}.lsp".format(token_type.lower()) + scope = f"meta.semantic-token.{token_type.lower()}.lsp" self.semantic_tokens.tokens.append(SemanticToken(r, token_type, token_modifiers)) if scope: scope_regions.setdefault(self._get_semantic_region_key_for_scope(scope), (scope, []))[1].append(r) @@ -679,12 +679,12 @@ def _draw_semantic_tokens_async(self) -> None: if region_key not in scope_regions.keys(): self.semantic_tokens.active_region_keys.remove(region_key) for sv in self.session_views: - sv.view.erase_regions("lsp_semantic_{}".format(region_key)) + sv.view.erase_regions(f"lsp_semantic_{region_key}") for region_key, (scope, regions) in scope_regions.items(): if region_key not in self.semantic_tokens.active_region_keys: self.semantic_tokens.active_region_keys.add(region_key) for sv in self.session_views: - sv.view.add_regions("lsp_semantic_{}".format(region_key), regions, scope, flags=SEMANTIC_TOKEN_FLAGS) + sv.view.add_regions(f"lsp_semantic_{region_key}", regions, scope, flags=SEMANTIC_TOKEN_FLAGS) def _get_semantic_region_key_for_scope(self, scope: str) -> int: if scope not in self._semantic_region_keys: @@ -694,12 +694,12 @@ def _get_semantic_region_key_for_scope(self, scope: str) -> int: def _clear_semantic_token_regions(self, view: sublime.View) -> None: for region_key in self.semantic_tokens.active_region_keys: - view.erase_regions("lsp_semantic_{}".format(region_key)) + view.erase_regions(f"lsp_semantic_{region_key}") def set_semantic_tokens_pending_refresh(self, needs_refresh: bool = True) -> None: self.semantic_tokens.needs_refresh = needs_refresh - def get_semantic_tokens(self) -> List[SemanticToken]: + def get_semantic_tokens(self) -> list[SemanticToken]: return self.semantic_tokens.tokens # --- textDocument/inlayHint ---------------------------------------------------------------------------------- @@ -716,7 +716,7 @@ def do_inlay_hints_async(self, view: sublime.View) -> None: } self.session.send_request_async(Request.inlayHint(params, view), self._on_inlay_hints_async) - def _on_inlay_hints_async(self, response: Union[List[InlayHint], None]) -> None: + def _on_inlay_hints_async(self, response: list[InlayHint] | None) -> None: if response: view = self.some_view() if not view: @@ -726,7 +726,7 @@ def _on_inlay_hints_async(self, response: Union[List[InlayHint], None]) -> None: else: sublime.set_timeout(lambda: self.remove_all_inlay_hints()) - def present_inlay_hints(self, phantoms: List[sublime.Phantom]) -> None: + def present_inlay_hints(self, phantoms: list[sublime.Phantom]) -> None: self._inlay_hints_phantom_set.update(phantoms) def set_inlay_hints_pending_refresh(self, needs_refresh: bool = True) -> None: @@ -745,4 +745,4 @@ def remove_all_inlay_hints(self) -> None: # ------------------------------------------------------------------------------------------------------------------ def __str__(self) -> str: - return '{}:{}:{}'.format(self.session.config.name, self._id, self.get_uri()) + return f'{self.session.config.name}:{self._id}:{self.get_uri()}' diff --git a/plugin/session_view.py b/plugin/session_view.py index 45c7bf75b..6d4d0c706 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -22,19 +22,19 @@ from .core.views import text_document_identifier from .diagnostics import DiagnosticsAnnotationsView from .session_buffer import SessionBuffer -from typing import Any, Dict, Generator, Iterable, List, Optional, Tuple +from typing import Any, Generator, Iterable from weakref import ref from weakref import WeakValueDictionary import functools import sublime -DIAGNOSTIC_TAG_VALUES: List[int] = [v for (k, v) in DiagnosticTag.__dict__.items() if not k.startswith('_')] +DIAGNOSTIC_TAG_VALUES: list[int] = [v for (k, v) in DiagnosticTag.__dict__.items() if not k.startswith('_')] class TagData: __slots__ = ('key', 'regions', 'scope') - def __init__(self, key: str, regions: List[sublime.Region] = [], scope: str = '') -> None: + def __init__(self, key: str, regions: list[sublime.Region] = [], scope: str = '') -> None: self.key = key self.regions = regions self.scope = scope @@ -51,14 +51,14 @@ class SessionView: TRIGGER_CHARACTERS_KEY = "completionProvider.triggerCharacters" CODE_ACTIONS_KEY = "lsp_code_action" - _session_buffers: WeakValueDictionary[Tuple[int, int], SessionBuffer] = WeakValueDictionary() + _session_buffers: WeakValueDictionary[tuple[int, int], SessionBuffer] = WeakValueDictionary() def __init__(self, listener: AbstractViewListener, session: Session, uri: DocumentUri) -> None: self._view = listener.view self._session = session self._diagnostic_annotations = DiagnosticsAnnotationsView(self._view, session.config.name) self._initialize_region_keys() - self._active_requests: Dict[int, ActiveRequest] = {} + self._active_requests: dict[int, ActiveRequest] = {} self._listener = ref(listener) self._code_lenses = CodeLensView(self._view) self.code_lenses_needs_refresh = False @@ -96,10 +96,10 @@ def on_before_remove(self) -> None: self.session.unregister_session_view_async(self) self.session.config.erase_view_status(self.view) for severity in reversed(range(1, len(DIAGNOSTIC_SEVERITY) + 1)): - self.view.erase_regions("{}_icon".format(self.diagnostics_key(severity, False))) - self.view.erase_regions("{}_underline".format(self.diagnostics_key(severity, False))) - self.view.erase_regions("{}_icon".format(self.diagnostics_key(severity, True))) - self.view.erase_regions("{}_underline".format(self.diagnostics_key(severity, True))) + self.view.erase_regions(f"{self.diagnostics_key(severity, False)}_icon") + self.view.erase_regions(f"{self.diagnostics_key(severity, False)}_underline") + self.view.erase_regions(f"{self.diagnostics_key(severity, True)}_icon") + self.view.erase_regions(f"{self.diagnostics_key(severity, True)}_underline") self.view.erase_regions("lsp_document_link") self.session_buffer.remove_session_view(self) listener = self.listener() @@ -134,35 +134,35 @@ def _initialize_region_keys(self) -> None: - gutter icons from region keys which were initialized _first_ are drawn For more context, see https://github.com/sublimelsp/LSP/issues/1593. """ - keys: List[str] = [] + keys: list[str] = [] r = [sublime.Region(0, 0)] document_highlight_style = userprefs().document_highlight_style hover_highlight_style = userprefs().hover_highlight_style line_modes = ["m", "s"] self.view.add_regions(self.CODE_ACTIONS_KEY, r) # code actions lightbulb icon should always be on top for key in range(1, 100): - keys.append("lsp_semantic_{}".format(key)) + keys.append(f"lsp_semantic_{key}") if document_highlight_style in ("background", "fill"): for kind in DOCUMENT_HIGHLIGHT_KIND_NAMES.values(): for mode in line_modes: - keys.append("lsp_highlight_{}{}".format(kind, mode)) + keys.append(f"lsp_highlight_{kind}{mode}") if hover_highlight_style in ("background", "fill"): keys.append(HOVER_HIGHLIGHT_KEY) for severity in range(1, 5): for mode in line_modes: for tag in range(1, 3): - keys.append("lsp{}d{}{}_tags_{}".format(self.session.config.name, mode, severity, tag)) + keys.append(f"lsp{self.session.config.name}d{mode}{severity}_tags_{tag}") keys.append("lsp_document_link") for severity in range(1, 5): for mode in line_modes: - keys.append("lsp{}d{}{}_icon".format(self.session.config.name, mode, severity)) + keys.append(f"lsp{self.session.config.name}d{mode}{severity}_icon") for severity in range(4, 0, -1): for mode in line_modes: - keys.append("lsp{}d{}{}_underline".format(self.session.config.name, mode, severity)) + keys.append(f"lsp{self.session.config.name}d{mode}{severity}_underline") if document_highlight_style in ("underline", "stippled"): for kind in DOCUMENT_HIGHLIGHT_KIND_NAMES.values(): for mode in line_modes: - keys.append("lsp_highlight_{}{}".format(kind, mode)) + keys.append(f"lsp_highlight_{kind}{mode}") if hover_highlight_style in ("underline", "stippled"): keys.append(HOVER_HIGHLIGHT_KEY) for key in keys: @@ -182,7 +182,7 @@ def _setup_auto_complete_triggers(self, settings: sublime.Settings) -> None: if isinstance(trigger_chars, list) or self.session.config.auto_complete_selector: self._apply_auto_complete_triggers(settings, trigger_chars or []) - def _register_auto_complete_triggers(self, registration_id: str, trigger_chars: List[str]) -> None: + def _register_auto_complete_triggers(self, registration_id: str, trigger_chars: list[str]) -> None: """Register trigger characters from a dynamic server registration.""" self._apply_auto_complete_triggers(self.view.settings(), trigger_chars, registration_id) @@ -191,7 +191,7 @@ def _unregister_auto_complete_triggers(self, registration_id: str) -> None: settings = self.view.settings() triggers = settings.get(self.AC_TRIGGERS_KEY) if isinstance(triggers, list): - new_triggers: List[Dict[str, str]] = [] + new_triggers: list[dict[str, str]] = [] name = self.session.config.name for trigger in triggers: if not isinstance(trigger, dict): @@ -204,8 +204,8 @@ def _unregister_auto_complete_triggers(self, registration_id: str) -> None: def _apply_auto_complete_triggers( self, settings: sublime.Settings, - trigger_chars: List[str], - registration_id: Optional[str] = None + trigger_chars: list[str], + registration_id: str | None = None ) -> None: """This method actually modifies the auto_complete_triggers entries for the view.""" selector = self.session.config.auto_complete_selector @@ -224,7 +224,7 @@ def _apply_auto_complete_triggers( if isinstance(registration_id, str): # This key is not used by Sublime, but is used as a "breadcrumb" as well, for dynamic registrations. trigger["registration_id"] = registration_id - triggers: List[Dict[str, str]] = settings.get(self.AC_TRIGGERS_KEY) or [] + triggers: list[dict[str, str]] = settings.get(self.AC_TRIGGERS_KEY) or [] triggers.append(trigger) settings.set(self.AC_TRIGGERS_KEY, triggers) @@ -248,22 +248,22 @@ def _decrement_hover_count(self) -> None: def reset_show_definitions(self) -> None: self.view.settings().erase(SHOW_DEFINITIONS_KEY) - def get_uri(self) -> Optional[DocumentUri]: + def get_uri(self) -> DocumentUri | None: listener = self.listener() return listener.get_uri() if listener else None - def get_language_id(self) -> Optional[str]: + def get_language_id(self) -> str | None: listener = self.listener() return listener.get_language_id() if listener else None - def get_view_for_group(self, group: int) -> Optional[sublime.View]: + def get_view_for_group(self, group: int) -> sublime.View | None: sheet = self.view.sheet() return self.view if sheet and sheet.group() == group else None - def get_capability_async(self, capability_path: str) -> Optional[Any]: + def get_capability_async(self, capability_path: str) -> Any | None: return self.session_buffer.get_capability(capability_path) - def on_capability_added_async(self, registration_id: str, capability_path: str, options: Dict[str, Any]) -> None: + def on_capability_added_async(self, registration_id: str, capability_path: str, options: dict[str, Any]) -> None: if capability_path == self.HOVER_PROVIDER_KEY: self._increment_hover_count() elif capability_path.startswith(self.COMPLETION_PROVIDER_KEY): @@ -275,7 +275,7 @@ def on_capability_added_async(self, registration_id: str, capability_path: str, if listener: listener.on_code_lens_capability_registered_async() - def on_capability_removed_async(self, registration_id: str, discarded_capabilities: Dict[str, Any]) -> None: + def on_capability_removed_async(self, registration_id: str, discarded_capabilities: dict[str, Any]) -> None: if self.HOVER_PROVIDER_KEY in discarded_capabilities: self._decrement_hover_count() elif self.COMPLETION_PROVIDER_KEY in discarded_capabilities: @@ -292,14 +292,14 @@ def shutdown_async(self) -> None: def diagnostics_key(self, severity: int, multiline: bool) -> str: return "lsp{}d{}{}".format(self.session.config.name, "m" if multiline else "s", severity) - def diagnostics_tag_scope(self, tag: int) -> Optional[str]: + def diagnostics_tag_scope(self, tag: int) -> str | None: for k, v in DiagnosticTag.__dict__.items(): if v == tag: - return 'markup.{}.lsp'.format(k.lower()) + return f'markup.{k.lower()}.lsp' return None def present_diagnostics_async( - self, is_view_visible: bool, data_per_severity: Dict[Tuple[int, bool], DiagnosticSeverityData] + self, is_view_visible: bool, data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData] ) -> None: flags = userprefs().diagnostics_highlight_style_flags() # for single lines multiline_flags = None if userprefs().show_multiline_diagnostics_highlights else sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.NO_UNDO # noqa: E501 @@ -316,7 +316,7 @@ def present_diagnostics_async( def _draw_diagnostics( self, - data_per_severity: Dict[Tuple[int, bool], DiagnosticSeverityData], + data_per_severity: dict[tuple[int, bool], DiagnosticSeverityData], severity: int, max_severity_level: int, flags: int, @@ -324,7 +324,7 @@ def _draw_diagnostics( ) -> None: ICON_FLAGS = sublime.HIDE_ON_MINIMAP | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE | sublime.NO_UNDO key = self.diagnostics_key(severity, multiline) - tags = {tag: TagData('{}_tags_{}'.format(key, tag)) for tag in DIAGNOSTIC_TAG_VALUES} + tags = {tag: TagData(f'{key}_tags_{tag}') for tag in DIAGNOSTIC_TAG_VALUES} data = data_per_severity.get((severity, multiline)) if data and severity <= max_severity_level: non_tag_regions = data.regions @@ -336,11 +336,11 @@ def _draw_diagnostics( tags[tag].scope = tag_scope else: non_tag_regions.extend(regions) - self.view.add_regions("{}_icon".format(key), non_tag_regions, data.scope, data.icon, ICON_FLAGS) - self.view.add_regions("{}_underline".format(key), non_tag_regions, data.scope, "", flags) + self.view.add_regions(f"{key}_icon", non_tag_regions, data.scope, data.icon, ICON_FLAGS) + self.view.add_regions(f"{key}_underline", non_tag_regions, data.scope, "", flags) else: - self.view.erase_regions("{}_icon".format(key)) - self.view.erase_regions("{}_underline".format(key)) + self.view.erase_regions(f"{key}_icon") + self.view.erase_regions(f"{key}_underline") for data in tags.values(): if data.regions: self.view.add_regions( @@ -354,7 +354,7 @@ def on_request_started_async(self, request_id: int, request: Request) -> None: def on_request_finished_async(self, request_id: int) -> None: self._active_requests.pop(request_id, None) - def on_request_progress(self, request_id: int, params: Dict[str, Any]) -> None: + def on_request_progress(self, request_id: int, params: dict[str, Any]) -> None: request = self._active_requests.get(request_id, None) if request: request.update_progress_async(params) @@ -391,7 +391,7 @@ def start_code_lenses_async(self) -> None: def clear_code_lenses_async(self) -> None: self._code_lenses.clear_view() - def _on_code_lenses_async(self, response: Optional[List[CodeLens]]) -> None: + def _on_code_lenses_async(self, response: list[CodeLens] | None) -> None: if not self._is_listener_alive() or not isinstance(response, list): return self._code_lenses.handle_response(self.session.config.name, response) @@ -405,7 +405,7 @@ def resolve_visible_code_lenses_async(self) -> None: return if self._code_lenses.is_empty(): return - promises: List[Promise[None]] = [Promise.resolve(None)] + promises: list[Promise[None]] = [Promise.resolve(None)] if self.get_capability_async('codeLensProvider.resolveProvider'): for code_lens in self._code_lenses.unresolved_visible_code_lenses(self.view.visible_region()): request = Request("codeLens/resolve", code_lens.data, self.view) @@ -426,4 +426,4 @@ def get_resolved_code_lenses_for_region(self, region: sublime.Region) -> Generat yield from self._code_lenses.get_resolved_code_lenses_for_region(region) def __str__(self) -> str: - return '{}:{}'.format(self.session.config.name, self.view.id()) + return f'{self.session.config.name}:{self.view.id()}' diff --git a/plugin/symbols.py b/plugin/symbols.py index 6948e9b37..cb7b99452 100644 --- a/plugin/symbols.py +++ b/plugin/symbols.py @@ -19,7 +19,7 @@ from .core.views import offset_to_point from .core.views import range_to_region from .core.views import text_document_identifier -from typing import Any, Dict, List, Optional, Tuple, TypedDict, Union +from typing import Any, List, TypedDict from typing import cast from typing_extensions import NotRequired, TypeGuard import functools @@ -30,7 +30,7 @@ SUPPRESS_INPUT_SETTING_KEY = 'lsp_suppress_input' -SYMBOL_KIND_NAMES: Dict[SymbolKind, str] = { +SYMBOL_KIND_NAMES: dict[SymbolKind, str] = { SymbolKind.File: "File", SymbolKind.Module: "Module", SymbolKind.Namespace: "Namespace", @@ -80,14 +80,14 @@ def is_document_symbol_value(val: Any) -> TypeGuard[DocumentSymbolValue]: def symbol_to_list_input_item( - item: Union[DocumentSymbol, WorkspaceSymbol, SymbolInformation], + item: DocumentSymbol | WorkspaceSymbol | SymbolInformation, hierarchy: str = '', - session_name: Optional[str] = None + session_name: str | None = None ) -> sublime.ListInputItem: name = item['name'] kind = item['kind'] st_kind = SYMBOL_KINDS.get(kind, sublime.KIND_AMBIGUOUS) - details: List[str] = [] + details: list[str] = [] deprecated = SymbolTag.Deprecated in (item.get('tags') or []) or item.get('deprecated', False) value = {'kind': kind, 'deprecated': deprecated} details_separator = " • " @@ -142,14 +142,14 @@ def run(self, _: sublime.Edit) -> None: class LspSelectionAddCommand(sublime_plugin.TextCommand): - def run(self, _: sublime.Edit, regions: List[Tuple[int, int]]) -> None: + def run(self, _: sublime.Edit, regions: list[tuple[int, int]]) -> None: for region in regions: self.view.sel().add(sublime.Region(*region)) class LspSelectionSetCommand(sublime_plugin.TextCommand): - def run(self, _: sublime.Edit, regions: List[Tuple[int, int]]) -> None: + def run(self, _: sublime.Edit, regions: list[tuple[int, int]]) -> None: self.view.sel().clear() for region in regions: self.view.sel().add(sublime.Region(*region)) @@ -161,7 +161,7 @@ class LspDocumentSymbolsCommand(LspTextCommand): def __init__(self, view: sublime.View) -> None: super().__init__(view) - self.items: List[sublime.ListInputItem] = [] + self.items: list[sublime.ListInputItem] = [] self.kind = 0 self.cached = False self.has_matching_symbols = True @@ -169,9 +169,9 @@ def __init__(self, view: sublime.View) -> None: def run( self, edit: sublime.Edit, - event: Optional[Dict[str, Any]] = None, + event: dict[str, Any] | None = None, kind: int = 0, - index: Optional[int] = None + index: int | None = None ) -> None: if index is None: if not self.has_matching_symbols: @@ -179,7 +179,7 @@ def run( window = self.view.window() if window: kind_name = SYMBOL_KIND_NAMES.get(cast(SymbolKind, self.kind)) - window.status_message('No symbols of kind "{}" in this file'.format(kind_name)) + window.status_message(f'No symbols of kind "{kind_name}" in this file') return self.kind = kind session = self.best_session(self.capability) @@ -189,7 +189,7 @@ def run( session.send_request( Request.documentSymbols(params, self.view), self.handle_response_async, self.handle_response_error) - def input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]: + def input(self, args: dict) -> sublime_plugin.CommandInputHandler | None: if self.cached: self.cached = False if self.kind and not any(item.value['kind'] == self.kind for item in self.items): @@ -208,7 +208,7 @@ def input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]: return DocumentSymbolsKindInputHandler(window, initial_value, self.view, self.items) return None - def handle_response_async(self, response: Union[List[DocumentSymbol], List[SymbolInformation], None]) -> None: + def handle_response_async(self, response: list[DocumentSymbol] | list[SymbolInformation] | None) -> None: self.items.clear() if response and self.view.is_valid(): if 'selectionRange' in response[0]: @@ -234,7 +234,7 @@ def _reset_suppress_input(self) -> None: def process_document_symbol_recursive( self, item: DocumentSymbol, hierarchy: str = '' - ) -> List[sublime.ListInputItem]: + ) -> list[sublime.ListInputItem]: name = item['name'] name_hierarchy = hierarchy + " > " + name if hierarchy else name items = [symbol_to_list_input_item(item, hierarchy)] @@ -250,7 +250,7 @@ def __init__( window: sublime.Window, initial_value: sublime.ListInputItem, view: sublime.View, - items: List[sublime.ListInputItem], + items: list[sublime.ListInputItem], ) -> None: super().__init__(window, initial_value) self.view = view @@ -264,7 +264,7 @@ def name(self) -> str: def placeholder(self) -> str: return "Symbol Kind" - def get_list_items(self) -> Tuple[List[sublime.ListInputItem], int]: + def get_list_items(self) -> tuple[list[sublime.ListInputItem], int]: items = [sublime.ListInputItem('All Kinds', 0, kind=sublime.KIND_AMBIGUOUS)] items.extend([ sublime.ListInputItem(SYMBOL_KIND_NAMES[lsp_kind], lsp_kind, kind=st_kind) @@ -281,7 +281,7 @@ def get_list_items(self) -> Tuple[List[sublime.ListInputItem], int]: def confirm(self, text: int) -> None: self.last_selected = text - def next_input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler]: + def next_input(self, args: dict) -> sublime_plugin.CommandInputHandler | None: kind = args.get('kind') if kind is not None: return DocumentSymbolsInputHandler(self.view, kind, self.items, self.old_selection) @@ -290,7 +290,7 @@ def next_input(self, args: dict) -> Optional[sublime_plugin.CommandInputHandler] class DocumentSymbolsInputHandler(sublime_plugin.ListInputHandler): def __init__( - self, view: sublime.View, kind: int, items: List[sublime.ListInputItem], old_selection: List[sublime.Region] + self, view: sublime.View, kind: int, items: list[sublime.ListInputItem], old_selection: list[sublime.Region] ) -> None: super().__init__() self.view = view @@ -301,7 +301,7 @@ def __init__( def name(self) -> str: return 'index' - def list_items(self) -> Tuple[List[sublime.ListInputItem], int]: + def list_items(self) -> tuple[list[sublime.ListInputItem], int]: items = [item for item in self.items if not self.kind or item.value['kind'] == self.kind] selected_index = 0 if self.old_selection: @@ -315,7 +315,7 @@ def list_items(self) -> Tuple[List[sublime.ListInputItem], int]: break return items, selected_index - def preview(self, text: Optional[DocumentSymbolValue]) -> Union[str, sublime.Html, None]: + def preview(self, text: DocumentSymbolValue | None) -> str | sublime.Html | None: if is_document_symbol_value(text): region = range_to_region(text['range'], self.view) self.view.run_command('lsp_selection_set', {'regions': [(region.a, region.b)]}) @@ -346,7 +346,7 @@ def run(self, symbol: WorkspaceSymbolValue) -> None: Request.resolveWorkspaceSymbol(symbol['workspaceSymbol']), # type: ignore functools.partial(self._on_resolved_symbol_async, session_name)) - def input(self, args: Dict[str, Any]) -> Optional[sublime_plugin.ListInputHandler]: + def input(self, args: dict[str, Any]) -> sublime_plugin.ListInputHandler | None: if 'symbol' not in args: return WorkspaceSymbolsInputHandler(self, args) return None @@ -366,7 +366,7 @@ def name(self) -> str: def placeholder(self) -> str: return "Start typing to search" - def preview(self, text: Optional[WorkspaceSymbolValue]) -> Union[str, sublime.Html, None]: + def preview(self, text: WorkspaceSymbolValue | None) -> str | sublime.Html | None: if isinstance(text, dict) and text.get('deprecated'): return "⚠ Deprecated" return "" @@ -376,7 +376,7 @@ def on_modified(self, text: str) -> None: return change_count = self.input_view.change_count() self.command = cast(LspWindowCommand, self.command) - promises: List[Promise[List[sublime.ListInputItem]]] = [] + promises: list[Promise[list[sublime.ListInputItem]]] = [] for session in self.command.sessions(): promises.append( session.send_request_task(Request.workspaceSymbol({"query": text})) @@ -384,13 +384,13 @@ def on_modified(self, text: str) -> None: Promise.all(promises).then(functools.partial(self._on_all_responses, change_count)) def _handle_response_async( - self, session_name: str, response: Union[List[SymbolInformation], List[WorkspaceSymbol], None] - ) -> List[sublime.ListInputItem]: + self, session_name: str, response: list[SymbolInformation] | list[WorkspaceSymbol] | None + ) -> list[sublime.ListInputItem]: return [symbol_to_list_input_item(item, session_name=session_name) for item in response] if response else [] - def _on_all_responses(self, change_count: int, item_lists: List[List[sublime.ListInputItem]]) -> None: + def _on_all_responses(self, change_count: int, item_lists: list[list[sublime.ListInputItem]]) -> None: if self.input_view and self.input_view.change_count() == change_count: - items: List[sublime.ListInputItem] = [] + items: list[sublime.ListInputItem] = [] for item_list in item_lists: items.extend(item_list) self.update(items) diff --git a/plugin/tooling.py b/plugin/tooling.py index f03d680a0..acfb42c0b 100644 --- a/plugin/tooling.py +++ b/plugin/tooling.py @@ -17,7 +17,7 @@ from base64 import b64decode from base64 import b64encode from subprocess import list2cmdline -from typing import Any, Callable, Dict, List, Optional, Tuple +from typing import Any, Callable from typing import cast import json import mdpopups @@ -29,7 +29,7 @@ import urllib.request -def _translate_description(translations: Optional[Dict[str, str]], descr: str) -> Tuple[str, bool]: +def _translate_description(translations: dict[str, str] | None, descr: str) -> tuple[str, bool]: """ Translate a placeholder description like "%foo.bar.baz" into an English translation. The translation map is the first argument. @@ -41,7 +41,7 @@ def _translate_description(translations: Optional[Dict[str, str]], descr: str) - return descr, False -def _preprocess_properties(translations: Optional[Dict[str, str]], properties: Dict[str, Any]) -> None: +def _preprocess_properties(translations: dict[str, str] | None, properties: dict[str, Any]) -> None: """ Preprocess the server settings from a package.json file: @@ -69,7 +69,7 @@ def _preprocess_properties(translations: Optional[Dict[str, str]], properties: D if not isinstance(enums, list): enums = v.get("markdownEnumDescriptions") if isinstance(enums, list): - new_enums: List[str] = [] + new_enums: list[str] = [] for descr in enums: descr, _ = _translate_description(translations, descr) new_enums.append(descr) @@ -94,7 +94,7 @@ def _preprocess_properties(translations: Optional[Dict[str, str]], properties: D def _enum_to_str(value: Any) -> str: if isinstance(value, str): - return '"{}"'.format(value) + return f'"{value}"' return str(value) @@ -104,13 +104,13 @@ def initial_text(self) -> str: return "foobar" def preview(self, text: str) -> str: - return "Suggested resource location: Packages/LSP-{0}/LSP-{0}.sublime-settings".format(text) + return f"Suggested resource location: Packages/LSP-{text}/LSP-{text}.sublime-settings" class LspParseVscodePackageJson(sublime_plugin.ApplicationCommand): def __init__(self) -> None: - self.view: Optional[sublime.View] = None + self.view: sublime.View | None = None def writeline(self, contents: str, indent: int = 0) -> None: if self.view is not None: @@ -119,7 +119,7 @@ def writeline(self, contents: str, indent: int = 0) -> None: def writeline4(self, contents: str) -> None: self.writeline(contents, indent=4) - def input(self, args: Dict[str, Any]) -> Optional[sublime_plugin.CommandInputHandler]: + def input(self, args: dict[str, Any]) -> sublime_plugin.CommandInputHandler | None: if "base_package_name" not in args: return BasePackageNameInputHandler() return None @@ -138,7 +138,7 @@ def run(self, base_package_name: str) -> None: try: package = json.loads(urllib.request.urlopen(base_url).read().decode("utf-8")) except Exception as ex: - sublime.error_message('Unable to load "{}": {}'.format(base_url, ex)) + sublime.error_message(f'Unable to load "{base_url}": {ex}') return # There might be a translations file as well. @@ -186,7 +186,7 @@ def run(self, base_package_name: str) -> None: if isinstance(description, str): for line in description.splitlines(): for wrapped_line in textwrap.wrap(line, width=100 - 8 - 3): - self.writeline4('// {}'.format(wrapped_line)) + self.writeline4(f'// {wrapped_line}') else: self.writeline4('// unknown setting') enum = v.get("enum") @@ -210,16 +210,16 @@ def run(self, base_package_name: str) -> None: elif typ == "number": value = 0 else: - self.writeline4('// UNKNOWN TYPE: {} <-- NEEDS ATTENTION'.format(typ)) + self.writeline4(f'// UNKNOWN TYPE: {typ} <-- NEEDS ATTENTION') value = "" value_lines = json.dumps(value, ensure_ascii=False, indent=4).splitlines() for index, line in enumerate(value_lines, 1): is_last_line = index == len(value_lines) terminator = ',' if is_last_line else '' if index == 1: - self.writeline4('"{}": {}{}'.format(k, line, terminator)) + self.writeline4(f'"{k}": {line}{terminator}') else: - self.writeline4('{}{}'.format(line, terminator)) + self.writeline4(f'{line}{terminator}') self.writeline("}") self.view.set_read_only(True) self.view = None @@ -235,10 +235,10 @@ def run(self, base_package_name: str) -> None: "settings": [ { "file_patterns": [ - "/LSP-{}.sublime-settings".format(base_package_name) + f"/LSP-{base_package_name}.sublime-settings" ], "schema": { - "$id": "sublime://settings/LSP-{}".format(base_package_name), + "$id": f"sublime://settings/LSP-{base_package_name}", "definitions": { "PluginConfig": { "properties": { @@ -254,7 +254,7 @@ def run(self, base_package_name: str) -> None: "$ref": "sublime://settings/LSP-plugin-base" }, { - "$ref": "sublime://settings/LSP-{}#/definitions/PluginConfig".format(base_package_name) # noqa: E501 + "$ref": f"sublime://settings/LSP-{base_package_name}#/definitions/PluginConfig" # noqa: E501 } ] } @@ -269,8 +269,8 @@ def run(self, base_package_name: str) -> None: "properties": { "LSP": { "properties": { - "LSP-{}".format(base_package_name): { - "$ref": "sublime://settings/LSP-{}#/definitions/PluginConfig".format(base_package_name) # noqa: E501 + f"LSP-{base_package_name}": { + "$ref": f"sublime://settings/LSP-{base_package_name}#/definitions/PluginConfig" # noqa: E501 } } } @@ -312,12 +312,12 @@ def run(self) -> None: wm.window.show_quick_panel(config_names, lambda index: self.on_selected(index, configs, active_view), placeholder='Select server to troubleshoot') - def on_selected(self, selected_index: int, configs: List[ClientConfig], active_view: sublime.View) -> None: + def on_selected(self, selected_index: int, configs: list[ClientConfig], active_view: sublime.View) -> None: if selected_index == -1: return config = configs[selected_index] output_sheet = mdpopups.new_html_sheet( - self.window, 'Server: {}'.format(config.name), '# Running server test...', + self.window, f'Server: {config.name}', '# Running server test...', css=css().sheets, wrapper_class=css().sheets_classname) sublime.set_timeout_async(lambda: self.test_run_server_async(config, self.window, active_view, output_sheet)) @@ -328,40 +328,40 @@ def test_run_server_async(self, config: ClientConfig, window: sublime.Window, lambda resolved_command, output, exit_code: self.update_sheet( config, active_view, output_sheet, resolved_command, output, exit_code)) # Store the instance so that it's not GC'ed before it's finished. - self.test_runner: Optional[ServerTestRunner] = server + self.test_runner: ServerTestRunner | None = server - def update_sheet(self, config: ClientConfig, active_view: Optional[sublime.View], output_sheet: sublime.HtmlSheet, - resolved_command: List[str], server_output: str, exit_code: int) -> None: + def update_sheet(self, config: ClientConfig, active_view: sublime.View | None, output_sheet: sublime.HtmlSheet, + resolved_command: list[str], server_output: str, exit_code: int) -> None: self.test_runner = None frontmatter = mdpopups.format_frontmatter({'allow_code_wrap': True}) contents = self.get_contents(config, active_view, resolved_command, server_output, exit_code) # The href needs to be encoded to avoid having markdown parser ruin it. copy_link = make_command_link('lsp_copy_to_clipboard_from_base64', 'Copy to clipboard', {'contents': b64encode(contents.encode()).decode()}) - formatted = '{}{}\n{}'.format(frontmatter, copy_link, contents) + formatted = f'{frontmatter}{copy_link}\n{contents}' mdpopups.update_html_sheet(output_sheet, formatted, css=css().sheets, wrapper_class=css().sheets_classname) - def get_contents(self, config: ClientConfig, active_view: Optional[sublime.View], resolved_command: List[str], + def get_contents(self, config: ClientConfig, active_view: sublime.View | None, resolved_command: list[str], server_output: str, exit_code: int) -> str: lines = [] def line(s: str) -> None: lines.append(s) - line('# Troubleshooting: {}'.format(config.name)) + line(f'# Troubleshooting: {config.name}') line('## Version') line(' - LSP: {}'.format('.'.join([str(n) for n in __version__]))) - line(' - Sublime Text: {}'.format(sublime.version())) + line(f' - Sublime Text: {sublime.version()}') line('## Server Test Run') - line(' - exit code: {}\n - output\n{}'.format(exit_code, self.code_block(server_output))) + line(f' - exit code: {exit_code}\n - output\n{self.code_block(server_output)}') line('## Server Configuration') - line(' - command\n{}'.format(self.json_dump(config.command))) + line(f' - command\n{self.json_dump(config.command)}') line(' - shell command\n{}'.format(self.code_block(list2cmdline(resolved_command), 'sh'))) - line(' - selector\n{}'.format(self.code_block(config.selector))) - line(' - priority_selector\n{}'.format(self.code_block(config.priority_selector))) + line(f' - selector\n{self.code_block(config.selector)}') + line(f' - priority_selector\n{self.code_block(config.priority_selector)}') line(' - init_options') line(self.json_dump(config.init_options.get())) line(' - settings') @@ -382,7 +382,7 @@ def line(s: str) -> None: if isinstance(settings['syntax'], str): syntax = sublime.syntax_from_path(settings['syntax']) if syntax: - line(' - base scope\n{}'.format(self.code_block(syntax.scope))) + line(f' - base scope\n{self.code_block(syntax.scope)}') else: line('no active view found!') @@ -391,9 +391,9 @@ def line(s: str) -> None: line(' - folders') line(self.json_dump(window.folders())) is_project = bool(window.project_file_name()) - line(' - is project: {}'.format(is_project)) + line(f' - is project: {is_project}') if is_project: - line(' - project data:\n{}'.format(self.json_dump(window.project_data()))) + line(f' - project data:\n{self.json_dump(window.project_data())}') line('\n## LSP configuration\n') lsp_settings_contents = self.read_resource('Packages/User/LSP.sublime-settings') @@ -403,7 +403,7 @@ def line(s: str) -> None: line('') line('## System PATH') - lines += [' - {}'.format(p) for p in os.environ['PATH'].split(os.pathsep)] + lines += [f' - {p}' for p in os.environ['PATH'].split(os.pathsep)] return '\n'.join(lines) @@ -411,9 +411,9 @@ def json_dump(self, contents: Any) -> str: return self.code_block(json.dumps(contents, indent=2, sort_keys=True, ensure_ascii=False), 'json') def code_block(self, contents: str, lang: str = '') -> str: - return '```{}\n{}\n```'.format(lang, contents) + return f'```{lang}\n{contents}\n```' - def read_resource(self, path: str) -> Optional[str]: + def read_resource(self, path: str) -> str | None: try: return sublime.load_resource(path) except Exception: @@ -436,7 +436,7 @@ def run(self) -> None: return view = self.window.new_file() view.set_scratch(True) - view.set_name("Window {} configs".format(self.window.id())) + view.set_name(f"Window {self.window.id()} configs") view.settings().set("word_wrap", False) view.set_syntax_file("Packages/Python/Python.sublime-syntax") for config in wm.get_config_manager().get_configs(): @@ -462,16 +462,16 @@ def run(self, edit: sublime.Edit) -> None: v = wm.window.new_file() v.set_scratch(True) v.assign_syntax("Packages/Markdown/Markdown.sublime-settings") - v.set_name("{} (capabilities)".format(os.path.basename(file_name))) + v.set_name(f"{os.path.basename(file_name)} (capabilities)") def p(s: str) -> None: v.run_command("append", {"characters": s + "\n"}) def print_capabilities(capabilities: Capabilities) -> str: - return "```json\n{}\n```".format(json.dumps(capabilities.get(), indent=4, sort_keys=True)) + return f"```json\n{json.dumps(capabilities.get(), indent=4, sort_keys=True)}\n```" for sv in listener.session_views_async(): - p("# {}\n".format(sv.session.config.name)) + p(f"# {sv.session.config.name}\n") p("## Global capabilities\n") p(print_capabilities(sv.session.capabilities) + "\n") p("## View-specific capabilities\n") @@ -492,12 +492,12 @@ def __init__( config: ClientConfig, window: sublime.Window, initiating_view: sublime.View, - on_close: Callable[[List[str], str, int], None] + on_close: Callable[[list[str], str, int], None] ) -> None: self._on_close = on_close - self._transport: Optional[Transport] = None - self._resolved_command: List[str] = [] - self._stderr_lines: List[str] = [] + self._transport: Transport | None = None + self._resolved_command: list[str] = [] + self._stderr_lines: list[str] = [] try: variables = extract_variables(window) plugin_class = get_plugin(config.name) @@ -512,7 +512,7 @@ def __init__( variables.update(additional_variables) cannot_start_reason = plugin_class.can_start(window, initiating_view, workspace_folders, config) if cannot_start_reason: - raise Exception('Plugin.can_start() prevented the start due to: {}'.format(cannot_start_reason)) + raise Exception(f'Plugin.can_start() prevented the start due to: {cannot_start_reason}') cwd = plugin_class.on_pre_start(window, initiating_view, workspace_folders, config) if not cwd and workspace_folders: cwd = workspace_folders[0].path @@ -527,13 +527,13 @@ def force_close_transport(self) -> None: if self._transport: self._transport.close() - def on_payload(self, payload: Dict[str, Any]) -> None: + def on_payload(self, payload: dict[str, Any]) -> None: pass def on_stderr_message(self, message: str) -> None: self._stderr_lines.append(message) - def on_transport_close(self, exit_code: int, exception: Optional[Exception]) -> None: + def on_transport_close(self, exit_code: int, exception: Exception | None) -> None: self._transport = None output = str(exception) if exception else '\n'.join(self._stderr_lines).rstrip() sublime.set_timeout(lambda: self._on_close(self._resolved_command, output, exit_code)) @@ -541,10 +541,10 @@ def on_transport_close(self, exit_code: int, exception: Optional[Exception]) -> class LspOnDoubleClickCommand(sublime_plugin.TextCommand): click_count = 0 - prev_command: Optional[str] = None - prev_args: Optional[Dict[Any, Any]] = None + prev_command: str | None = None + prev_args: dict[Any, Any] | None = None - def run(self, edit: sublime.Edit, command: str, args: Dict[Any, Any]) -> None: + def run(self, edit: sublime.Edit, command: str, args: dict[Any, Any]) -> None: if self.prev_command != command or self.prev_args != args: self.click_count = 0 self.prev_command = command diff --git a/scripts/release.py b/scripts/release.py index 0c59872ed..765428781 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -1,5 +1,4 @@ #!/usr/bin/env python3 -# -*- coding: utf-8 -*- from typing import Generator, List, Optional, Tuple import argparse @@ -14,7 +13,7 @@ MESSAGE_DIR = 'messages' MESSAGE_PATH = os.path.join(PACKAGE_PATH, MESSAGE_DIR) -with open(os.path.join(PACKAGE_PATH, '.release.json'), 'r') as f: +with open(os.path.join(PACKAGE_PATH, '.release.json')) as f: CONFIGURATION = json.load(f) # Project configuration @@ -25,12 +24,12 @@ # The prefix to use for release version number. For example with prefix "4000" the version will be "4000-x.y.z". RELEASE_VERSION_PREFIX = CONFIGURATION['publish_version_prefix'] or '' # The name of the settings file to get the release token from ("github_token" setting) -SETTINGS = '{}.sublime-settings'.format(__package__) +SETTINGS = f'{__package__}.sublime-settings' PYTHON_VERSION_PATH = CONFIGURATION.get('python_version_path', None) def get_message(fname: str) -> str: - with open(fname, 'r', encoding='utf-8') as file: + with open(fname, encoding='utf-8') as file: message = file.read() return message @@ -66,14 +65,15 @@ def parse_version(version: str) -> Tuple[int, int, int]: match = re.match( r'(?:(?P[^.-]+)\-)?(?P\d+)\.(?P\d+)\.(?P\d+)(?:-.+)?', version) if match: - prefix, major, minor, patch = match.groups() + _prefix, major, minor, patch = match.groups() return int(major), int(minor), int(patch) else: return 0, 0, 0 + def get_version_with_prefix(version: str) -> str: if RELEASE_VERSION_PREFIX: - return '{}-{}'.format(RELEASE_VERSION_PREFIX, version) + return f'{RELEASE_VERSION_PREFIX}-{version}' return version @@ -106,7 +106,7 @@ def build_release(args: argparse.Namespace) -> None: put_message(os.path.join(PACKAGE_PATH, 'VERSION'), version) if PYTHON_VERSION_PATH: version_tuple = parse_version(version) - put_message(PYTHON_VERSION_PATH, '__version__ = {}\n'.format(version_tuple)) + put_message(PYTHON_VERSION_PATH, f'__version__ = {version_tuple}\n') build_messages_json(history) commit_release(version) print("Release %s created!" % version) @@ -121,16 +121,16 @@ def publish_release(args: argparse.Namespace) -> None: version = get_message(os.path.join(PACKAGE_PATH, 'VERSION')) version_with_prefix = get_version_with_prefix(version) - repo_url = 'git@github.com:{}'.format(GITHUB_REPO) + repo_url = f'git@github.com:{GITHUB_REPO}' # push release branch to server git('push', repo_url, RELEASE_BRANCH) # push tags to server git('push', repo_url, 'tag', version_with_prefix) # publish the release - post_url = '/repos/{}/releases'.format(GITHUB_REPO) + post_url = f'/repos/{GITHUB_REPO}/releases' headers = { - 'Authorization': 'token {}'.format(args.token), + 'Authorization': f'token {args.token}', 'User-Agent': 'Sublime Text', 'Content-type': 'application/json', } @@ -166,7 +166,7 @@ def publish_release(args: argparse.Namespace) -> None: """ if __name__ == '__main__': parser = argparse.ArgumentParser( - description='Buils and Publishes {} Releases'.format(__package__)) + description=f'Buils and Publishes {__package__} Releases') subparsers = parser.add_subparsers(help='Available commands') build_parser = subparsers.add_parser('build', help='Build a release') build_parser.set_defaults(func=build_release) diff --git a/tests/server.py b/tests/server.py index 123f651bc..38c5960d3 100644 --- a/tests/server.py +++ b/tests/server.py @@ -23,7 +23,7 @@ from __future__ import annotations from argparse import ArgumentParser from enum import IntEnum -from typing import Any, Callable, Dict, List, Optional, Tuple, Union, Iterable, Awaitable +from typing import Any, Callable, Dict, List, Union, Iterable, Awaitable import asyncio import json import os @@ -111,14 +111,14 @@ def dump(payload: PayloadLike) -> bytes: separators=(",", ":")).encode(ENCODING) -def content_length(line: bytes) -> Optional[int]: +def content_length(line: bytes) -> int | None: if line.startswith(b'Content-Length: '): _, value = line.split(b'Content-Length: ') value = value.strip() try: return int(value) except ValueError: - raise ValueError("Invalid Content-Length header: {}".format(value)) + raise ValueError(f"Invalid Content-Length header: {value}") return None @@ -147,7 +147,7 @@ class SimpleRequest(Request): def __init__(self) -> None: self.cv = asyncio.Condition() self.result: PayloadLike = None - self.error: Optional[Error] = None + self.error: Error | None = None async def on_result(self, params: PayloadLike) -> None: self.result = params @@ -166,16 +166,16 @@ def __init__(self, reader: asyncio.StreamReader, writer: asyncio.StreamWriter) - self._reader = reader self._writer = writer - self._response_handlers: Dict[Any, Request] = {} - self._request_handlers: Dict[str, Callable[[PayloadLike], Awaitable[PayloadLike]]] = {} - self._notification_handlers: Dict[str, Callable[[PayloadLike], Awaitable[None]]] = {} + self._response_handlers: dict[Any, Request] = {} + self._request_handlers: dict[str, Callable[[PayloadLike], Awaitable[PayloadLike]]] = {} + self._notification_handlers: dict[str, Callable[[PayloadLike], Awaitable[None]]] = {} # initialize/shutdown/exit dance self._received_shutdown = False # properties used for testing purposes - self._responses: List[Tuple[str, PayloadLike]] = [] - self._received: Dict[str, PayloadLike] = {} + self._responses: list[tuple[str, PayloadLike]] = [] + self._received: dict[str, PayloadLike] = {} self._received_cv = asyncio.Condition() self._install_handlers() @@ -245,8 +245,8 @@ def _on_request(self, request_method: str, handler: Callable[[PayloadLike], Awai def _on_notification(self, notification_method: str, handler: Callable[[PayloadLike], Awaitable[None]]) -> None: self._notification_handlers[notification_method] = handler - async def _handle(self, typestr: str, message: Dict[str, Any], handlers: Dict[str, Callable], - request_id: Optional[int]) -> None: + async def _handle(self, typestr: str, message: dict[str, Any], handlers: dict[str, Callable], + request_id: int | None) -> None: method = message.get("method", "") params = message.get("params") unhandled = True @@ -263,7 +263,7 @@ async def _handle(self, typestr: str, message: Dict[str, Any], handlers: Dict[st self._reply(request_id, mocked_response) elif request_id is not None: self._error(request_id, Error( - ErrorCode.MethodNotFound, "method '{}' not found".format(method))) + ErrorCode.MethodNotFound, f"method '{method}' not found")) else: if unhandled: self._log(f"unhandled {typestr} {method}") @@ -285,7 +285,7 @@ async def _handle(self, typestr: str, message: Dict[str, Any], handlers: Dict[st if not self._received_shutdown: self._notify("window/logMessage", {"type": MessageType.error, "message": str(ex)}) - def _get_mocked_response(self, method: str) -> Union[PayloadLike, bool]: + def _get_mocked_response(self, method: str) -> PayloadLike | bool: for response in self._responses: resp_method, resp_payload = response if resp_method == method: @@ -296,7 +296,7 @@ def _get_mocked_response(self, method: str) -> Union[PayloadLike, bool]: async def _handle_body(self, body: bytes) -> None: try: await self._receive_payload(json.loads(body)) - except IOError as ex: + except OSError as ex: self._log(f"malformed {ENCODING}: {ex}") except UnicodeDecodeError as ex: self._log(f"malformed {ENCODING}: {ex}") @@ -357,7 +357,7 @@ async def _send_notification(self, params: PayloadLike) -> PayloadLike: return None async def _get_received(self, params: PayloadLike) -> PayloadLike: - method, payload = self._validate_request_params(params) + method, _payload = self._validate_request_params(params) async with self._received_cv: while True: try: @@ -370,7 +370,7 @@ async def _fake_request(self, params: PayloadLike) -> PayloadLike: method, payload = self._validate_request_params(params) return await self.request(method, payload) - def _validate_request_params(self, params: PayloadLike) -> Tuple[str, Optional[Union[Dict, List]]]: + def _validate_request_params(self, params: PayloadLike) -> tuple[str, dict | list | None]: if not isinstance(params, dict): raise Error(ErrorCode.InvalidParams, "expected params to be a dictionary") if "method" not in params: @@ -402,7 +402,7 @@ async def _on_exit(self, params: PayloadLike) -> None: # START: https://stackoverflow.com/a/52702646/990142 -async def stdio() -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: +async def stdio() -> tuple[asyncio.StreamReader, asyncio.StreamWriter]: loop = asyncio.get_event_loop() if sys.platform == 'win32': return _win32_stdio(loop) @@ -410,7 +410,7 @@ async def stdio() -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: return await _unix_stdio(loop) -async def _unix_stdio(loop: asyncio.AbstractEventLoop) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: +async def _unix_stdio(loop: asyncio.AbstractEventLoop) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]: reader = asyncio.StreamReader(loop=loop) def reader_factory() -> asyncio.StreamReaderProtocol: @@ -426,7 +426,7 @@ def writer_factory() -> asyncio.streams.FlowControlMixin: return reader, writer -def _win32_stdio(loop: asyncio.AbstractEventLoop) -> Tuple[asyncio.StreamReader, asyncio.StreamWriter]: +def _win32_stdio(loop: asyncio.AbstractEventLoop) -> tuple[asyncio.StreamReader, asyncio.StreamWriter]: # no support for asyncio stdio yet on Windows, see https://bugs.python.org/issue26832 # use an executor to read from stdin and write to stdout @@ -436,7 +436,7 @@ class Reader: def __init__(self, loop: asyncio.AbstractEventLoop) -> None: self.loop = loop self.stdin = sys.stdin.buffer - self.__exception: Optional[Exception] = None + self.__exception: Exception | None = None def at_eof(self) -> bool: return self.__exception is not None @@ -461,7 +461,7 @@ class Writer: def __init__(self, loop: asyncio.AbstractEventLoop) -> None: self.loop = loop - self.buffer: List[bytes] = [] + self.buffer: list[bytes] = [] self.stdout = sys.stdout.buffer def write(self, data: bytes) -> None: @@ -483,7 +483,7 @@ def do_blocking_drain() -> None: # END: https://stackoverflow.com/a/52702646/990142 -async def main(tcp_port: Optional[int] = None) -> bool: +async def main(tcp_port: int | None = None) -> bool: if tcp_port is not None: class ClientConnectedCallback: diff --git a/tests/setup.py b/tests/setup.py index 95c382b30..6d3a17b62 100644 --- a/tests/setup.py +++ b/tests/setup.py @@ -10,7 +10,7 @@ from os.path import join from sublime_plugin import view_event_listeners from test_mocks import basic_responses -from typing import Any, Dict, Generator, List, Optional, Tuple, Union +from typing import Any, Generator from unittesting import DeferrableTestCase import sublime @@ -18,16 +18,11 @@ CI = any(key in environ for key in ("TRAVIS", "CI", "GITHUB_ACTIONS")) TIMEOUT_TIME = 10000 if CI else 2000 -text_config = ClientConfig( - name="textls", - selector="text.plain", - command=[], - tcp_port=None) +text_config = ClientConfig(name="textls", selector="text.plain", command=[], tcp_port=None) class YieldPromise: - - __slots__ = ('__done', '__result') + __slots__ = ("__done", "__result") def __init__(self) -> None: self.__done = False @@ -49,7 +44,8 @@ def make_stdio_test_config() -> ClientConfig: name="TEST", command=["python3", join("$packages", "LSP", "tests", "server.py")], selector="text.plain", - enabled=True) + enabled=True, + ) def make_tcp_test_config() -> ClientConfig: @@ -58,7 +54,8 @@ def make_tcp_test_config() -> ClientConfig: command=["python3", join("$packages", "LSP", "tests", "server.py"), "--tcp-port", "$port"], selector="text.plain", tcp_port=0, # select a free one for me - enabled=True) + enabled=True, + ) def add_config(config): @@ -69,7 +66,7 @@ def remove_config(config): client_configs.remove_for_testing(config) -def close_test_view(view: Optional[sublime.View]) -> Generator: +def close_test_view(view: sublime.View | None) -> Generator: if view: view.set_scratch(True) yield {"condition": lambda: not view.is_loading(), "timeout": TIMEOUT_TIME} @@ -81,7 +78,6 @@ def expand(s: str, w: sublime.Window) -> str: class TextDocumentTestCase(DeferrableTestCase): - @classmethod def get_stdio_test_config(cls) -> ClientConfig: return make_stdio_test_config() @@ -92,7 +88,7 @@ def setUpClass(cls) -> Generator: test_name = cls.get_test_name() server_capabilities = cls.get_test_server_capabilities() window = sublime.active_window() - filename = expand(join("$packages", "LSP", "tests", "{}.txt".format(test_name)), window) + filename = expand(join("$packages", "LSP", "tests", f"{test_name}.txt"), window) open_view = window.find_open_file(filename) yield from close_test_view(open_view) cls.config = cls.get_stdio_test_config() @@ -102,9 +98,7 @@ def setUpClass(cls) -> Generator: cls.view = window.open_file(filename) yield {"condition": lambda: not cls.view.is_loading(), "timeout": TIMEOUT_TIME} yield cls.ensure_document_listener_created - yield { - "condition": lambda: cls.wm.get_session(cls.config.name, filename) is not None, - "timeout": TIMEOUT_TIME} + yield {"condition": lambda: cls.wm.get_session(cls.config.name, filename) is not None, "timeout": TIMEOUT_TIME} cls.session = cls.wm.get_session(cls.config.name, filename) yield {"condition": lambda: cls.session.state == ClientStates.READY, "timeout": TIMEOUT_TIME} cls.initialize_params = yield from cls.await_message("initialize") @@ -113,7 +107,7 @@ def setUpClass(cls) -> Generator: def setUp(self) -> Generator: window = sublime.active_window() - filename = expand(join("$packages", "LSP", "tests", "{}.txt".format(self.get_test_name())), window) + filename = expand(join("$packages", "LSP", "tests", f"{self.get_test_name()}.txt"), window) open_view = window.find_open_file(filename) if not open_view: self.__class__.view = window.open_file(filename) @@ -122,7 +116,7 @@ def setUp(self) -> Generator: self.init_view_settings() yield self.ensure_document_listener_created params = yield from self.await_message("textDocument/didOpen") - self.assertEquals(params['textDocument']['version'], 0) + self.assertEqual(params["textDocument"]["version"], 0) @classmethod def get_test_name(cls) -> str: @@ -157,7 +151,7 @@ def ensure_document_listener_created(cls) -> bool: return False @classmethod - def await_message(cls, method: str, promise: Optional[YieldPromise] = None) -> Generator: + def await_message(cls, method: str, promise: YieldPromise | None = None) -> Generator: """ Awaits until server receives a request with a specified method. @@ -199,7 +193,7 @@ def on_error(params: Any) -> None: return promise @classmethod - def await_promise(cls, promise: Union[YieldPromise, Promise]) -> Generator: + def await_promise(cls, promise: YieldPromise | Promise) -> Generator: if isinstance(promise, YieldPromise): yielder = promise else: @@ -208,11 +202,13 @@ def await_promise(cls, promise: Union[YieldPromise, Promise]) -> Generator: yield {"condition": yielder, "timeout": TIMEOUT_TIME} return yielder.result() - def await_run_code_action(self, code_action: Dict[str, Any]) -> Generator: + def await_run_code_action(self, code_action: dict[str, Any]) -> Generator: promise = YieldPromise() sublime.set_timeout_async( - lambda: self.session.run_code_action_async(code_action, progress=False, view=self.view) - .then(promise.fulfill)) + lambda: self.session.run_code_action_async(code_action, progress=False, view=self.view).then( + promise.fulfill + ) + ) yield from self.await_promise(promise) def set_response(self, method: str, response: Any) -> None: @@ -220,7 +216,7 @@ def set_response(self, method: str, response: Any) -> None: assert self.session # mypy self.session.send_notification(Notification("$test/setResponse", {"method": method, "response": response})) - def set_responses(self, responses: List[Tuple[str, Any]]) -> Generator: + def set_responses(self, responses: list[tuple[str, Any]]) -> Generator: self.assertIsNotNone(self.session) assert self.session # mypy promise = YieldPromise() @@ -251,7 +247,7 @@ def error_handler(params: Any) -> None: yield from self.await_promise(promise) def await_clear_view_and_save(self) -> Generator: - assert self.view # type: Optional[sublime.View] + assert isinstance(self.view, sublime.View) self.view.run_command("select_all") self.view.run_command("left_delete") self.view.run_command("save") @@ -259,7 +255,7 @@ def await_clear_view_and_save(self) -> Generator: yield from self.await_message("textDocument/didSave") def await_view_change(self, expected_change_count: int) -> Generator: - assert self.view # type: Optional[sublime.View] + assert isinstance(self.view, sublime.View) def condition() -> bool: nonlocal self @@ -271,7 +267,7 @@ def condition() -> bool: yield {"condition": condition, "timeout": TIMEOUT_TIME} def insert_characters(self, characters: str) -> int: - assert self.view # type: Optional[sublime.View] + assert isinstance(self.view, sublime.View) self.view.run_command("insert", {"characters": characters}) return self.view.change_count() diff --git a/tests/test_code_actions.py b/tests/test_code_actions.py index 6ca704895..7932d6004 100644 --- a/tests/test_code_actions.py +++ b/tests/test_code_actions.py @@ -9,14 +9,14 @@ from LSP.plugin.core.views import versioned_text_document_identifier from setup import TextDocumentTestCase from test_single_document import TEST_FILE_PATH -from typing import Any, Dict, Generator, List, Optional, Tuple +from typing import Any, Generator import unittest import sublime TEST_FILE_URI = filename_to_uri(TEST_FILE_PATH) -def edit_to_lsp(edit: Tuple[str, Range]) -> Dict[str, Any]: +def edit_to_lsp(edit: tuple[str, Range]) -> dict[str, Any]: return {"newText": edit[0], "range": edit[1]} @@ -27,7 +27,7 @@ def range_from_points(start: Point, end: Point) -> Range: } -def create_code_action_edit(view: sublime.View, version: int, edits: List[Tuple[str, Range]]) -> Dict[str, Any]: +def create_code_action_edit(view: sublime.View, version: int, edits: list[tuple[str, Range]]) -> dict[str, Any]: return { "documentChanges": [ { @@ -38,15 +38,15 @@ def create_code_action_edit(view: sublime.View, version: int, edits: List[Tuple[ } -def create_command(command_name: str, command_args: Optional[List[Any]] = None) -> Dict[str, Any]: - result: Dict[str, Any] = {"command": command_name} +def create_command(command_name: str, command_args: list[Any] | None = None) -> dict[str, Any]: + result: dict[str, Any] = {"command": command_name} if command_args is not None: result["arguments"] = command_args return result -def create_test_code_action(view: sublime.View, version: int, edits: List[Tuple[str, Range]], - kind: Optional[str] = None) -> Dict[str, Any]: +def create_test_code_action(view: sublime.View, version: int, edits: list[tuple[str, Range]], + kind: str | None = None) -> dict[str, Any]: action = { "title": "Fix errors", "edit": create_code_action_edit(view, version, edits) @@ -56,8 +56,8 @@ def create_test_code_action(view: sublime.View, version: int, edits: List[Tuple[ return action -def create_test_code_action2(command_name: str, command_args: Optional[List[Any]] = None, - kind: Optional[str] = None) -> Dict[str, Any]: +def create_test_code_action2(command_name: str, command_args: list[Any] | None = None, + kind: str | None = None) -> dict[str, Any]: action = { "title": "Fix errors", "command": create_command(command_name, command_args) @@ -67,7 +67,7 @@ def create_test_code_action2(command_name: str, command_args: Optional[List[Any] return action -def create_disabled_code_action(view: sublime.View, version: int, edits: List[Tuple[str, Range]]) -> Dict[str, Any]: +def create_disabled_code_action(view: sublime.View, version: int, edits: list[tuple[str, Range]]) -> dict[str, Any]: action = { "title": "Fix errors", "edit": create_code_action_edit(view, version, edits), @@ -78,8 +78,8 @@ def create_disabled_code_action(view: sublime.View, version: int, edits: List[Tu return action -def create_test_diagnostics(diagnostics: List[Tuple[str, Range]]) -> Dict: - def diagnostic_to_lsp(diagnostic: Tuple[str, Range]) -> Dict: +def create_test_diagnostics(diagnostics: list[tuple[str, Range]]) -> dict: + def diagnostic_to_lsp(diagnostic: tuple[str, Range]) -> dict: message, range = diagnostic return { "message": message, @@ -122,8 +122,8 @@ def test_applies_matching_kind(self) -> Generator: self.view.run_command('lsp_save', {'async': True}) yield from self.await_message('textDocument/codeAction') yield from self.await_message('textDocument/didSave') - self.assertEquals(entire_content(self.view), 'const x = 1;') - self.assertEquals(self.view.is_dirty(), False) + self.assertEqual(entire_content(self.view), 'const x = 1;') + self.assertEqual(self.view.is_dirty(), False) def test_requests_with_diagnostics(self) -> Generator: yield from self._setup_document_with_missing_semicolon() @@ -137,11 +137,11 @@ def test_requests_with_diagnostics(self) -> Generator: self.set_response('textDocument/codeAction', [code_action]) self.view.run_command('lsp_save', {'async': True}) code_action_request = yield from self.await_message('textDocument/codeAction') - self.assertEquals(len(code_action_request['context']['diagnostics']), 1) - self.assertEquals(code_action_request['context']['diagnostics'][0]['message'], 'Missing semicolon') + self.assertEqual(len(code_action_request['context']['diagnostics']), 1) + self.assertEqual(code_action_request['context']['diagnostics'][0]['message'], 'Missing semicolon') yield from self.await_message('textDocument/didSave') - self.assertEquals(entire_content(self.view), 'const x = 1;') - self.assertEquals(self.view.is_dirty(), False) + self.assertEqual(entire_content(self.view), 'const x = 1;') + self.assertEqual(self.view.is_dirty(), False) def test_applies_only_one_pass(self) -> Generator: self.insert_characters('const x = 1') @@ -180,7 +180,7 @@ def test_applies_only_one_pass(self) -> Generator: self.view.run_command('lsp_save', {'async': True}) # Wait for the view to be saved yield lambda: not self.view.is_dirty() - self.assertEquals(entire_content(self.view), 'const x = 1;') + self.assertEqual(entire_content(self.view), 'const x = 1;') def test_applies_immediately_after_text_change(self) -> Generator: self.insert_characters('const x = 1') @@ -195,16 +195,16 @@ def test_applies_immediately_after_text_change(self) -> Generator: self.view.run_command('lsp_save', {'async': True}) yield from self.await_message('textDocument/codeAction') yield from self.await_message('textDocument/didSave') - self.assertEquals(entire_content(self.view), 'const x = 1;') - self.assertEquals(self.view.is_dirty(), False) + self.assertEqual(entire_content(self.view), 'const x = 1;') + self.assertEqual(self.view.is_dirty(), False) def test_no_fix_on_non_matching_kind(self) -> Generator: yield from self._setup_document_with_missing_semicolon() initial_content = 'const x = 1' self.view.run_command('lsp_save', {'async': True}) yield from self.await_message('textDocument/didSave') - self.assertEquals(entire_content(self.view), initial_content) - self.assertEquals(self.view.is_dirty(), False) + self.assertEqual(entire_content(self.view), initial_content) + self.assertEqual(self.view.is_dirty(), False) def test_does_not_apply_unsupported_kind(self) -> Generator: yield from self._setup_document_with_missing_semicolon() @@ -218,7 +218,7 @@ def test_does_not_apply_unsupported_kind(self) -> Generator: self.set_response('textDocument/codeAction', [code_action]) self.view.run_command('lsp_save', {'async': True}) yield from self.await_message('textDocument/didSave') - self.assertEquals(entire_content(self.view), 'const x = 1') + self.assertEqual(entire_content(self.view), 'const x = 1') def _setup_document_with_missing_semicolon(self) -> Generator: self.insert_characters('const x = 1') @@ -234,23 +234,23 @@ def _setup_document_with_missing_semicolon(self) -> Generator: class CodeActionMatchingTestCase(unittest.TestCase): def test_does_not_match(self) -> None: actual = get_matching_on_save_kinds({'a.x': True}, ['a.b']) - expected: List[str] = [] - self.assertEquals(actual, expected) + expected: list[str] = [] + self.assertEqual(actual, expected) def test_matches_exact_action(self) -> None: actual = get_matching_on_save_kinds({'a.b': True}, ['a.b']) expected = ['a.b'] - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_matches_more_specific_action(self) -> None: actual = get_matching_on_save_kinds({'a.b': True}, ['a.b.c']) expected = ['a.b.c'] - self.assertEquals(actual, expected) + self.assertEqual(actual, expected) def test_does_not_match_disabled_action(self) -> None: actual = get_matching_on_save_kinds({'a.b': True, 'a.b.c': False}, ['a.b.c']) - expected: List[str] = [] - self.assertEquals(actual, expected) + expected: list[str] = [] + self.assertEqual(actual, expected) def test_kind_matching(self) -> None: # Positive @@ -299,15 +299,15 @@ def test_requests_with_diagnostics(self) -> Generator: self.view.run_command('lsp_selection_set', {"regions": [(0, 3)]}) # Select a and b. yield 100 params = yield from self.await_message('textDocument/codeAction') - self.assertEquals(params['range']['start']['line'], 0) - self.assertEquals(params['range']['start']['character'], 0) - self.assertEquals(params['range']['end']['line'], 1) - self.assertEquals(params['range']['end']['character'], 1) - self.assertEquals(len(params['context']['diagnostics']), 2) + self.assertEqual(params['range']['start']['line'], 0) + self.assertEqual(params['range']['start']['character'], 0) + self.assertEqual(params['range']['end']['line'], 1) + self.assertEqual(params['range']['end']['character'], 1) + self.assertEqual(len(params['context']['diagnostics']), 2) annotations_range = self.view.get_regions(SessionView.CODE_ACTIONS_KEY) - self.assertEquals(len(annotations_range), 1) - self.assertEquals(annotations_range[0].a, 3) - self.assertEquals(annotations_range[0].b, 0) + self.assertEqual(len(annotations_range), 1) + self.assertEqual(annotations_range[0].a, 3) + self.assertEqual(annotations_range[0].b, 0) def test_requests_with_no_diagnostics(self) -> Generator: initial_content = 'a\nb\nc' @@ -321,15 +321,15 @@ def test_requests_with_no_diagnostics(self) -> Generator: self.view.run_command('lsp_selection_set', {"regions": [(0, 3)]}) # Select a and b. yield 100 params = yield from self.await_message('textDocument/codeAction') - self.assertEquals(params['range']['start']['line'], 0) - self.assertEquals(params['range']['start']['character'], 0) - self.assertEquals(params['range']['end']['line'], 1) - self.assertEquals(params['range']['end']['character'], 1) - self.assertEquals(len(params['context']['diagnostics']), 0) + self.assertEqual(params['range']['start']['line'], 0) + self.assertEqual(params['range']['start']['character'], 0) + self.assertEqual(params['range']['end']['line'], 1) + self.assertEqual(params['range']['end']['character'], 1) + self.assertEqual(len(params['context']['diagnostics']), 0) annotations_range = self.view.get_regions(SessionView.CODE_ACTIONS_KEY) - self.assertEquals(len(annotations_range), 1) - self.assertEquals(annotations_range[0].a, 3) - self.assertEquals(annotations_range[0].b, 0) + self.assertEqual(len(annotations_range), 1) + self.assertEqual(annotations_range[0].a, 3) + self.assertEqual(annotations_range[0].b, 0) def test_excludes_disabled_code_actions(self) -> Generator: initial_content = 'a\n' @@ -345,7 +345,7 @@ def test_excludes_disabled_code_actions(self) -> Generator: yield 100 yield from self.await_message('textDocument/codeAction') code_action_ranges = self.view.get_regions(SessionView.CODE_ACTIONS_KEY) - self.assertEquals(len(code_action_ranges), 0) + self.assertEqual(len(code_action_ranges), 0) def test_extends_range_to_include_diagnostics(self) -> Generator: self.insert_characters('x diagnostic') @@ -361,11 +361,11 @@ def test_extends_range_to_include_diagnostics(self) -> Generator: yield 100 params = yield from self.await_message('textDocument/codeAction') # Range should be extended to include range of all intersecting diagnostics - self.assertEquals(params['range']['start']['line'], 0) - self.assertEquals(params['range']['start']['character'], 0) - self.assertEquals(params['range']['end']['line'], 0) - self.assertEquals(params['range']['end']['character'], 12) - self.assertEquals(len(params['context']['diagnostics']), 2) + self.assertEqual(params['range']['start']['line'], 0) + self.assertEqual(params['range']['start']['character'], 0) + self.assertEqual(params['range']['end']['line'], 0) + self.assertEqual(params['range']['end']['character'], 12) + self.assertEqual(len(params['context']['diagnostics']), 2) class CodeActionsTestCase(TextDocumentTestCase): @@ -387,11 +387,11 @@ def test_requests_code_actions_on_newly_published_diagnostics(self) -> Generator ]) ) params = yield from self.await_message('textDocument/codeAction') - self.assertEquals(params['range']['start']['line'], 1) - self.assertEquals(params['range']['start']['character'], 0) - self.assertEquals(params['range']['end']['line'], 1) - self.assertEquals(params['range']['end']['character'], 1) - self.assertEquals(len(params['context']['diagnostics']), 1) + self.assertEqual(params['range']['start']['line'], 1) + self.assertEqual(params['range']['start']['character'], 0) + self.assertEqual(params['range']['end']['line'], 1) + self.assertEqual(params['range']['end']['character'], 1) + self.assertEqual(len(params['context']['diagnostics']), 1) def test_applies_code_action_with_matching_document_version(self) -> Generator: code_action = create_test_code_action(self.view, 3, [ @@ -403,7 +403,7 @@ def test_applies_code_action_with_matching_document_version(self) -> Generator: self.assertEqual(self.view.change_count(), 3) yield from self.await_run_code_action(code_action) # yield from self.await_message('codeAction/resolve') - self.assertEquals(entire_content(self.view), 'c\nd') + self.assertEqual(entire_content(self.view), 'c\nd') def test_does_not_apply_with_nonmatching_document_version(self) -> Generator: initial_content = 'a\nb' @@ -414,7 +414,7 @@ def test_does_not_apply_with_nonmatching_document_version(self) -> Generator: self.insert_characters(initial_content) yield from self.await_message("textDocument/didChange") yield from self.await_run_code_action(code_action) - self.assertEquals(entire_content(self.view), initial_content) + self.assertEqual(entire_content(self.view), initial_content) def test_runs_command_in_resolved_code_action(self) -> Generator: code_action = create_test_code_action2("dosomethinguseful", ["1", 0, {"hello": "there"}]) @@ -432,7 +432,7 @@ def test_runs_command_in_resolved_code_action(self) -> Generator: yield from self.await_message('codeAction/resolve') params = yield from self.await_message('workspace/executeCommand') self.assertEqual(params, {"command": "dosomethinguseful", "arguments": ["1", 0, {"hello": "there"}]}) - self.assertEquals(entire_content(self.view), 'c\nd') + self.assertEqual(entire_content(self.view), 'c\nd') # Keep this test last as it breaks pyls! def test_applies_correctly_after_emoji(self) -> Generator: @@ -442,4 +442,4 @@ def test_applies_correctly_after_emoji(self) -> Generator: ("bye", range_from_points(Point(0, 3), Point(0, 5))), ]) yield from self.await_run_code_action(code_action) - self.assertEquals(entire_content(self.view), '🕵️bye') + self.assertEqual(entire_content(self.view), '🕵️bye') diff --git a/tests/test_completion.py b/tests/test_completion.py index 5eae358b0..0476cabb6 100644 --- a/tests/test_completion.py +++ b/tests/test_completion.py @@ -9,7 +9,7 @@ from LSP.plugin.core.protocol import CompletionItemTag from LSP.plugin.core.protocol import InsertTextFormat from setup import TextDocumentTestCase -from typing import Any, Callable, Dict, Generator, List, Optional +from typing import Any, Callable, Generator from unittest import TestCase import sublime @@ -79,7 +79,7 @@ def shift_select_completion(self) -> Generator: def read_file(self) -> str: return self.view.substr(sublime.Region(0, self.view.size())) - def verify(self, *, completion_items: List[Dict[str, Any]], insert_text: str, expected_text: str) -> Generator: + def verify(self, *, completion_items: list[dict[str, Any]], insert_text: str, expected_text: str) -> Generator: if insert_text: self.type(insert_text) self.set_response("textDocument/completion", completion_items) @@ -421,7 +421,7 @@ def test_prefix_should_include_the_dollar_sign(self) -> Generator: yield from self.select_completion() yield from self.await_message('textDocument/completion') - self.assertEquals(self.read_file(), '\n') + self.assertEqual(self.read_file(), '\n') def test_fuzzy_match_plaintext_insert_text(self) -> Generator: yield from self.verify( @@ -467,7 +467,7 @@ def test_fuzzy_match_snippet_text_edit(self) -> Generator: insert_text='aab', expected_text='aaca') - def verify_multi_cursor(self, completion: Dict[str, Any]) -> Generator: + def verify_multi_cursor(self, completion: dict[str, Any]) -> Generator: """ This checks whether `fd` gets replaced by `fmod` when the cursor is at `fd|`. Turning the `d` into an `m` is an important part of the test. @@ -687,7 +687,7 @@ def check( resolve_support: bool, expected_regex: str, label: str, - label_details: Optional[CompletionItemLabelDetails] + label_details: CompletionItemLabelDetails | None ) -> None: lsp: CompletionItem = {"label": label, "filterText": "force_label_to_go_into_st_detail_field"} if label_details is not None: @@ -738,7 +738,7 @@ def check( resolve_support: bool, expected_regex: str, label: str, - label_details: Optional[CompletionItemLabelDetails] + label_details: CompletionItemLabelDetails | None ) -> None: lsp: CompletionItem = {"label": label} if label_details is not None: @@ -913,10 +913,10 @@ def _verify_completion( ) -> None: item = format_completion( payload, index=0, can_resolve_completion_items=False, session_name='abc', item_defaults={}, view_id=0) - self.assertEquals(item.trigger, trigger) - self.assertEquals(item.annotation, annotation) - self.assertEquals(item.details, details) - self.assertEquals(item.flags, flags) + self.assertEqual(item.trigger, trigger) + self.assertEqual(item.annotation, annotation) + self.assertEqual(item.details, details) + self.assertEqual(item.flags, flags) def test_label(self) -> None: self._verify_completion( diff --git a/tests/test_configs.py b/tests/test_configs.py index 3cdade0b2..ef1e2ea94 100644 --- a/tests/test_configs.py +++ b/tests/test_configs.py @@ -81,7 +81,7 @@ def test_transport_config_extends_env_path(self): transport_config = config.resolve_transport_config({}) original_path = environ.copy()['PATH'] resolved_path = transport_config.env['PATH'] - self.assertEqual(resolved_path, '/a/b/{}{}'.format(pathsep, original_path)) + self.assertEqual(resolved_path, f'/a/b/{pathsep}{original_path}') def test_list_in_environment(self): settings = { diff --git a/tests/test_edit.py b/tests/test_edit.py index 9607ecdfe..7b2e99dee 100644 --- a/tests/test_edit.py +++ b/tests/test_edit.py @@ -9,7 +9,6 @@ from LSP.plugin.edit import temporary_setting from setup import TextDocumentTestCase from test_protocol import LSP_RANGE -from typing import List import sublime import unittest @@ -231,7 +230,7 @@ class ApplyDocumentEditTestCase(TextDocumentTestCase): def test_applies_text_edit(self) -> None: self.insert_characters('abc') - edits: List[TextEdit] = [{ + edits: list[TextEdit] = [{ 'newText': 'x$0y', 'range': { 'start': { @@ -245,11 +244,11 @@ def test_applies_text_edit(self) -> None: } }] apply_text_edits(self.view, edits) - self.assertEquals(entire_content(self.view), 'ax$0yc') + self.assertEqual(entire_content(self.view), 'ax$0yc') def test_applies_text_edit_with_placeholder(self) -> None: self.insert_characters('abc') - edits: List[TextEdit] = [{ + edits: list[TextEdit] = [{ 'newText': 'x$0y', 'range': { 'start': { @@ -263,7 +262,7 @@ def test_applies_text_edit_with_placeholder(self) -> None: } }] apply_text_edits(self.view, edits, process_placeholders=True) - self.assertEquals(entire_content(self.view), 'axyc') + self.assertEqual(entire_content(self.view), 'axyc') self.assertEqual(len(self.view.sel()), 1) self.assertEqual(self.view.sel()[0], sublime.Region(2, 2)) @@ -282,9 +281,9 @@ def test_applies_multiple_text_edits_with_placeholders(self) -> None: } } } - edits: List[TextEdit] = [newline_edit, newline_edit] + edits: list[TextEdit] = [newline_edit, newline_edit] apply_text_edits(self.view, edits, process_placeholders=True) - self.assertEquals(entire_content(self.view), 'a\n\nb') + self.assertEqual(entire_content(self.view), 'a\n\nb') self.assertEqual(len(self.view.sel()), 2) self.assertEqual(self.view.sel()[0], sublime.Region(2, 2)) self.assertEqual(self.view.sel()[1], sublime.Region(3, 3)) diff --git a/tests/test_file_watcher.py b/tests/test_file_watcher.py index b9cf0c289..990163d3d 100644 --- a/tests/test_file_watcher.py +++ b/tests/test_file_watcher.py @@ -11,7 +11,7 @@ from os.path import join from setup import expand from setup import TextDocumentTestCase -from typing import Generator, List, Optional +from typing import Generator import sublime import unittest @@ -33,15 +33,15 @@ def setup_workspace_folder() -> str: class TestFileWatcher(FileWatcher): # The list of watchers created by active sessions. - _active_watchers: List[TestFileWatcher] = [] + _active_watchers: list[TestFileWatcher] = [] @classmethod def create( cls, root_path: str, - patterns: List[str], - events: List[FileWatcherEventType], - ignores: List[str], + patterns: list[str], + events: list[FileWatcherEventType], + ignores: list[str], handler: FileWatcherProtocol ) -> TestFileWatcher: watcher = TestFileWatcher(root_path, patterns, events, ignores, handler) @@ -51,9 +51,9 @@ def create( def __init__( self, root_path: str, - patterns: List[str], - events: List[FileWatcherEventType], - ignores: List[str], + patterns: list[str], + events: list[FileWatcherEventType], + ignores: list[str], handler: FileWatcherProtocol ) -> None: self.root_path = root_path @@ -66,7 +66,7 @@ def destroy(self) -> None: self.handler = None self._active_watchers.remove(self) - def trigger_event(self, events: List[FileWatcherEvent]) -> None: + def trigger_event(self, events: list[FileWatcherEvent]) -> None: def trigger_async(): if self.handler: @@ -250,10 +250,10 @@ def test_project_relative_patterns(self): def _verify_patterns( self, - patterns: List[str], - expected: List[str], + patterns: list[str], + expected: list[str], is_directory_pattern: bool, - root_path: Optional[str] = None + root_path: str | None = None ) -> None: glob_patterns = [ sublime_pattern_to_glob(pattern, is_directory_pattern=is_directory_pattern, root_path=root_path) diff --git a/tests/test_mocks.py b/tests/test_mocks.py index f155d5315..b833a6026 100644 --- a/tests/test_mocks.py +++ b/tests/test_mocks.py @@ -4,7 +4,7 @@ from LSP.plugin.core.protocol import Request from LSP.plugin.core.protocol import Response from LSP.plugin.core.types import ClientConfig -from typing import Any, Callable, List +from typing import Any, Callable TEST_CONFIG = ClientConfig(name="test", command=[], selector="text.plain", tcp_port=None) @@ -41,10 +41,10 @@ } -class MockSession(object): +class MockSession: def __init__(self, async_response=None) -> None: self.responses = basic_responses - self._notifications: List[Notification] = [] + self._notifications: list[Notification] = [] self._async_response_callback = async_response def send_request(self, request: Request, on_success: Callable, on_error: Callable = None) -> None: diff --git a/tests/test_server_requests.py b/tests/test_server_requests.py index 3e24d555e..7bef59bf0 100644 --- a/tests/test_server_requests.py +++ b/tests/test_server_requests.py @@ -5,13 +5,13 @@ from LSP.plugin.core.types import ClientConfig from LSP.plugin.core.url import filename_to_uri from setup import TextDocumentTestCase -from typing import Any, Dict, Generator, List, Optional +from typing import Any, Generator import os import sublime import tempfile -def get_auto_complete_trigger(sb: SessionBufferProtocol) -> Optional[List[Dict[str, str]]]: +def get_auto_complete_trigger(sb: SessionBufferProtocol) -> list[dict[str, str]] | None: for sv in sb.session_views: triggers = sv.view.settings().get("auto_complete_triggers") for trigger in triggers: @@ -47,7 +47,7 @@ def test_m_workspace_configuration(self) -> Generator: class TempPlugin: @classmethod - def additional_variables(cls) -> Optional[Dict[str, str]]: + def additional_variables(cls) -> dict[str, str] | None: return {"hello": "X", "world": "Y"} self.session._plugin_class = TempPlugin # type: ignore @@ -72,7 +72,7 @@ def test_m_workspace_applyEdit_with_nontrivial_promises(self) -> Generator: initial_text = ["a b", "c d"] file_paths = [] for i in range(0, 2): - file_paths.append(os.path.join(dirpath, "file{}.txt".format(i))) + file_paths.append(os.path.join(dirpath, f"file{i}.txt")) with open(file_paths[-1], "w") as fp: fp.write(initial_text[i]) yield from verify( diff --git a/tests/test_session.py b/tests/test_session.py index 86c42155d..ffb0cdd09 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -11,7 +11,7 @@ from LSP.plugin.core.types import ClientConfig from LSP.plugin.core.workspace import WorkspaceFolder from test_mocks import TEST_CONFIG -from typing import Any, Dict, Generator, List, Optional +from typing import Any, Generator import sublime import unittest import unittest.mock @@ -26,19 +26,19 @@ def __init__(self, window: sublime.Window) -> None: def window(self) -> sublime.Window: return self._window - def sessions(self, view: sublime.View, capability: Optional[str] = None) -> Generator[Session, None, None]: + def sessions(self, view: sublime.View, capability: str | None = None) -> Generator[Session, None, None]: pass - def get_project_path(self, file_name: str) -> Optional[str]: + def get_project_path(self, file_name: str) -> str | None: return None - def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> Optional[str]: + def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> str | None: return None def start_async(self, configuration: ClientConfig, initiating_view: sublime.View) -> None: pass - def on_post_exit_async(self, session: Session, exit_code: int, exception: Optional[Exception]) -> None: + def on_post_exit_async(self, session: Session, exit_code: int, exception: Exception | None) -> None: pass def on_diagnostics_updated(self) -> None: @@ -62,7 +62,7 @@ def outgoing_request(self, request_id: int, method: str, params: Any, blocking: def outgoing_notification(self, method: str, params: Any) -> None: pass - def incoming_response(self, request_id: Optional[int], params: Any, is_error: bool, blocking: bool) -> None: + def incoming_response(self, request_id: int | None, params: Any, is_error: bool, blocking: bool) -> None: pass def incoming_request(self, request_id: Any, method: str, params: Any) -> None: @@ -80,10 +80,10 @@ def __init__(self, session: Session, mock_uri: str, mock_language_id: str) -> No self.mock_uri = mock_uri self.mock_language_id = mock_language_id - def get_uri(self) -> Optional[DocumentUri]: + def get_uri(self) -> DocumentUri | None: return self.mock_uri - def get_language_id(self) -> Optional[str]: + def get_language_id(self) -> str | None: return self.mock_language_id def register_capability_async( @@ -91,7 +91,7 @@ def register_capability_async( registration_id: str, capability_path: str, registration_path: str, - options: Dict[str, Any] + options: dict[str, Any] ) -> None: pass @@ -103,7 +103,7 @@ def unregister_capability_async( ) -> None: pass - def on_diagnostics_async(self, raw_diagnostics: List[Diagnostic], version: Optional[int]) -> None: + def on_diagnostics_async(self, raw_diagnostics: list[Diagnostic], version: int | None) -> None: pass diff --git a/tests/test_single_document.py b/tests/test_single_document.py index 186bfb993..7ae66f027 100644 --- a/tests/test_single_document.py +++ b/tests/test_single_document.py @@ -111,7 +111,7 @@ def test_formats_on_save(self) -> Generator: yield from self.await_message("textDocument/didChange") yield from self.await_message("textDocument/didSave") text = self.view.substr(sublime.Region(0, self.view.size())) - self.assertEquals("BBB", text) + self.assertEqual("BBB", text) yield from self.await_clear_view_and_save() def test_hover_info(self) -> Generator: @@ -201,9 +201,9 @@ def test_tabs_are_respected_even_when_translate_tabs_to_spaces_is_set_to_true(se def __run_formatting_test( self, - original: 'Iterable[str]', - expected: 'Iterable[str]', - file_changes: 'List[Tuple[Tuple[int, int], Tuple[int, int], str]]' + original: Iterable[str], + expected: Iterable[str], + file_changes: list[tuple[tuple[int, int], tuple[int, int], str]] ) -> Generator: assert self.view original_change_count = self.insert_characters(''.join(original)) @@ -217,7 +217,7 @@ def __run_formatting_test( yield from self.await_message('textDocument/formatting') yield from self.await_view_change(original_change_count + len(file_changes)) edited_content = self.view.substr(sublime.Region(0, self.view.size())) - self.assertEquals(edited_content, ''.join(expected)) + self.assertEqual(edited_content, ''.join(expected)) def __run_goto_test(self, response: list, text_document_request: str, subl_command_suffix: str) -> Generator: assert self.view @@ -225,9 +225,9 @@ def __run_goto_test(self, response: list, text_document_request: str, subl_comma # Put the cursor back at the start of the buffer, otherwise is_at_word fails in goto.py. self.view.sel().clear() self.view.sel().add(sublime.Region(0, 0)) - method = 'textDocument/{}'.format(text_document_request) + method = f'textDocument/{text_document_request}' self.set_response(method, response) - self.view.run_command('lsp_symbol_{}'.format(subl_command_suffix)) + self.view.run_command(f'lsp_symbol_{subl_command_suffix}') yield from self.await_message(method) def condition() -> bool: @@ -420,5 +420,5 @@ def test_will_save_wait_until(self) -> Generator: yield from self.await_message("textDocument/didChange") yield from self.await_message("textDocument/didSave") text = self.view.substr(sublime.Region(0, self.view.size())) - self.assertEquals("BBB", text) + self.assertEqual("BBB", text) yield from self.await_clear_view_and_save() diff --git a/tests/test_types.py b/tests/test_types.py index 35dc30fd0..5b81e1da5 100644 --- a/tests/test_types.py +++ b/tests/test_types.py @@ -2,7 +2,6 @@ from LSP.plugin.core.types import diff from LSP.plugin.core.types import basescope2languageid from LSP.plugin.core.types import DocumentSelector -from typing import List from unittest.mock import MagicMock import sublime import unittest @@ -45,7 +44,7 @@ def test_completely_new(self) -> None: class TestDocumentSelector(unittest.TestCase): def setUp(self) -> None: - self._opened_views: List[sublime.View] = [] + self._opened_views: list[sublime.View] = [] def tearDown(self) -> None: for view in self._opened_views: