diff --git a/plugin/core/registry.py b/plugin/core/registry.py index b2af40c95..60b339ae2 100644 --- a/plugin/core/registry.py +++ b/plugin/core/registry.py @@ -1,8 +1,8 @@ from .configurations import ConfigManager +from .sessions import AbstractViewListener from .sessions import Session from .settings import client_configs from .typing import Optional, Any, Generator, Iterable -from .windows import AbstractViewListener from .windows import WindowRegistry import sublime import sublime_plugin diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index fad39d2eb..d41b6709e 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -19,6 +19,7 @@ from .protocol import Command from .protocol import CompletionItemTag from .protocol import Diagnostic +from .protocol import DiagnosticSeverity from .protocol import DiagnosticTag from .protocol import DidChangeWatchedFilesRegistrationOptions from .protocol import DocumentUri @@ -45,7 +46,7 @@ from .types import method_to_capability from .types import SettingsRegistration from .types import sublime_pattern_to_glob -from .typing import Callable, cast, Dict, Any, Optional, List, Tuple, Generator, Type, Protocol, Mapping, Union +from .typing import Callable, cast, Dict, Any, Optional, List, Tuple, Generator, Iterable, Type, Protocol, Sequence, Mapping, Union # noqa: E501 from .url import filename_to_uri from .url import parse_uri from .version import __version__ @@ -301,7 +302,10 @@ def get_initialize_params(variables: Dict[str, str], workspace_folders: List[Wor "valueSet": symbol_tag_value_set } }, - "configuration": True + "configuration": True, + "codeLens": { + "refreshSupport": True + } } window_capabilities = { "showDocument": { @@ -340,10 +344,21 @@ def get_initialize_params(variables: Dict[str, str], workspace_folders: List[Wor class SessionViewProtocol(Protocol): - session = None # type: Session - view = None # type: sublime.View - listener = None # type: Any - session_buffer = None # type: Any + @property + def session(self) -> 'Session': + ... + + @property + def view(self) -> sublime.View: + ... + + @property + def listener(self) -> 'weakref.ref[AbstractViewListener]': + ... + + @property + def session_buffer(self) -> 'SessionBufferProtocol': + ... def get_uri(self) -> Optional[str]: ... @@ -381,11 +396,19 @@ 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[CodeLens, None, None]: ... + def start_code_lenses_async(self) -> None: + ... + class SessionBufferProtocol(Protocol): - session = None # type: Session - session_views = None # type: WeakSet[SessionViewProtocol] + @property + def session(self) -> 'Session': + ... + + @property + def session_views(self) -> 'WeakSet[SessionViewProtocol]': + ... def get_uri(self) -> Optional[str]: ... @@ -417,6 +440,93 @@ def on_diagnostics_async(self, raw_diagnostics: List[Diagnostic], version: Optio ... +class AbstractViewListener(metaclass=ABCMeta): + + TOTAL_ERRORS_AND_WARNINGS_STATUS_KEY = "lsp_total_errors_and_warnings" + + view = None # type: sublime.View + + @abstractmethod + def session_async(self, capability_path: str, point: Optional[int] = None) -> Optional['Session']: + raise NotImplementedError() + + @abstractmethod + def sessions_async(self, capability_path: Optional[str] = None) -> Generator['Session', None, None]: + raise NotImplementedError() + + @abstractmethod + def session_views_async(self) -> Iterable['SessionViewProtocol']: + raise NotImplementedError() + + @abstractmethod + def on_session_initialized_async(self, session: 'Session') -> None: + raise NotImplementedError() + + @abstractmethod + def on_session_shutdown_async(self, session: 'Session') -> None: + raise NotImplementedError() + + @abstractmethod + def diagnostics_async( + self + ) -> Iterable[Tuple['SessionBufferProtocol', Sequence[Tuple[Diagnostic, sublime.Region]]]]: + raise NotImplementedError() + + @abstractmethod + def diagnostics_intersecting_region_async( + self, + region: sublime.Region + ) -> Tuple[Sequence[Tuple['SessionBufferProtocol', Sequence[Diagnostic]]], sublime.Region]: + raise NotImplementedError() + + @abstractmethod + def diagnostics_touching_point_async( + self, + pt: int, + max_diagnostic_severity_level: int = DiagnosticSeverity.Hint + ) -> Tuple[Sequence[Tuple['SessionBufferProtocol', Sequence[Diagnostic]]], sublime.Region]: + raise NotImplementedError() + + def diagnostics_intersecting_async( + self, + region_or_point: Union[sublime.Region, int] + ) -> Tuple[Sequence[Tuple['SessionBufferProtocol', Sequence[Diagnostic]]], sublime.Region]: + if isinstance(region_or_point, int): + return self.diagnostics_touching_point_async(region_or_point) + elif region_or_point.empty(): + return self.diagnostics_touching_point_async(region_or_point.a) + else: + return self.diagnostics_intersecting_region_async(region_or_point) + + @abstractmethod + def on_diagnostics_updated_async(self) -> None: + raise NotImplementedError() + + @abstractmethod + def on_code_lens_capability_registered_async(self) -> None: + raise NotImplementedError() + + @abstractmethod + def get_language_id(self) -> str: + raise NotImplementedError() + + @abstractmethod + def get_uri(self) -> str: + raise NotImplementedError() + + @abstractmethod + def do_signature_help_async(self, manual: bool) -> None: + raise NotImplementedError() + + @abstractmethod + def navigate_signature_help(self, forward: bool) -> None: + raise NotImplementedError() + + @abstractmethod + def on_post_move_window_async(self) -> None: + raise NotImplementedError() + + class AbstractPlugin(metaclass=ABCMeta): """ Inherit from this class to handle non-standard requests and notifications. @@ -1317,6 +1427,13 @@ def m_workspace_applyEdit(self, params: Any, request_id: Any) -> None: self._apply_workspace_edit_async(params.get('edit', {})).then( lambda _: self.send_response(Response(request_id, {"applied": True}))) + def m_workspace_codeLens_refresh(self, _: Any, request_id: Any) -> None: + """handles the workspace/codeLens/refresh request""" + if self.uses_plugin(): + for sv in self.session_views_async(): + sv.start_code_lenses_async() + self.send_response(Response(request_id, None)) + def m_textDocument_publishDiagnostics(self, params: Any) -> None: """handles the textDocument/publishDiagnostics notification""" uri = params["uri"] diff --git a/plugin/core/transports.py b/plugin/core/transports.py index 1d0eabdfd..1784502a0 100644 --- a/plugin/core/transports.py +++ b/plugin/core/transports.py @@ -142,6 +142,8 @@ def _read_loop(self) -> None: continue def invoke(p: T) -> None: + if self._closed: + return callback_object = self._callback_object() if callback_object: callback_object.on_payload(p) diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 8f72918ef..704312bba 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -8,30 +8,25 @@ from .message_request_handler import MessageRequestHandler from .panels import log_server_message from .promise import Promise -from .protocol import Diagnostic -from .protocol import DiagnosticSeverity from .protocol import DocumentUri from .protocol import Error from .protocol import Location +from .sessions import AbstractViewListener from .sessions import get_plugin from .sessions import Logger from .sessions import Manager from .sessions import Session -from .sessions import SessionBufferProtocol -from .sessions import SessionViewProtocol from .settings import userprefs from .transports import create_transport from .types import ClientConfig from .types import matches_pattern -from .typing import Optional, Any, Dict, Deque, List, Generator, Tuple, Iterable, Sequence, Union +from .typing import Optional, Any, Dict, Deque, List, Generator, Tuple from .url import parse_uri from .views import extract_variables from .views import format_diagnostic_for_panel from .views import make_link from .workspace import ProjectFolders from .workspace import sorted_workspace_folders -from abc import ABCMeta -from abc import abstractmethod from collections import OrderedDict from collections import deque from subprocess import CalledProcessError @@ -48,91 +43,6 @@ _NO_DIAGNOSTICS_PLACEHOLDER = " No diagnostics. Well done!" -class AbstractViewListener(metaclass=ABCMeta): - - TOTAL_ERRORS_AND_WARNINGS_STATUS_KEY = "lsp_total_errors_and_warnings" - - view = None # type: sublime.View - - @abstractmethod - def session_async(self, capability_path: str, point: Optional[int] = None) -> Optional[Session]: - raise NotImplementedError() - - @abstractmethod - def sessions_async(self, capability_path: Optional[str] = None) -> Generator[Session, None, None]: - raise NotImplementedError() - - @abstractmethod - def session_views_async(self) -> Iterable[SessionViewProtocol]: - raise NotImplementedError() - - @abstractmethod - def on_session_initialized_async(self, session: Session) -> None: - raise NotImplementedError() - - @abstractmethod - def on_session_shutdown_async(self, session: Session) -> None: - raise NotImplementedError() - - @abstractmethod - def diagnostics_async(self) -> Iterable[Tuple[SessionBufferProtocol, Sequence[Tuple[Diagnostic, sublime.Region]]]]: - raise NotImplementedError() - - @abstractmethod - def diagnostics_intersecting_region_async( - self, - region: sublime.Region - ) -> Tuple[Sequence[Tuple[SessionBufferProtocol, Sequence[Diagnostic]]], sublime.Region]: - raise NotImplementedError() - - @abstractmethod - def diagnostics_touching_point_async( - self, - pt: int, - max_diagnostic_severity_level: int = DiagnosticSeverity.Hint - ) -> Tuple[Sequence[Tuple[SessionBufferProtocol, Sequence[Diagnostic]]], sublime.Region]: - raise NotImplementedError() - - def diagnostics_intersecting_async( - self, - region_or_point: Union[sublime.Region, int] - ) -> Tuple[Sequence[Tuple[SessionBufferProtocol, Sequence[Diagnostic]]], sublime.Region]: - if isinstance(region_or_point, int): - return self.diagnostics_touching_point_async(region_or_point) - elif region_or_point.empty(): - return self.diagnostics_touching_point_async(region_or_point.a) - else: - return self.diagnostics_intersecting_region_async(region_or_point) - - @abstractmethod - def on_diagnostics_updated_async(self) -> None: - raise NotImplementedError() - - @abstractmethod - def on_code_lens_capability_registered_async(self) -> None: - raise NotImplementedError() - - @abstractmethod - def get_language_id(self) -> str: - raise NotImplementedError() - - @abstractmethod - def get_uri(self) -> str: - raise NotImplementedError() - - @abstractmethod - def do_signature_help_async(self, manual: bool) -> None: - raise NotImplementedError() - - @abstractmethod - def navigate_signature_help(self, forward: bool) -> None: - raise NotImplementedError() - - @abstractmethod - def on_post_move_window_async(self) -> None: - raise NotImplementedError() - - def extract_message(params: Any) -> str: return params.get("message", "???") if isinstance(params, dict) else "???" diff --git a/plugin/documents.py b/plugin/documents.py index aa6b9af37..afd438c7c 100644 --- a/plugin/documents.py +++ b/plugin/documents.py @@ -14,6 +14,7 @@ from .core.protocol import SignatureHelp from .core.registry import best_session from .core.registry import windows +from .core.sessions import AbstractViewListener from .core.sessions import Session from .core.settings import userprefs from .core.signature_help import SigHelp @@ -32,7 +33,6 @@ from .core.views import show_lsp_popup from .core.views import text_document_position_params from .core.views import update_lsp_popup -from .core.windows import AbstractViewListener from .core.windows import WindowManager from .session_buffer import SessionBuffer from .session_view import SessionView @@ -136,8 +136,6 @@ class DocumentSyncListener(sublime_plugin.ViewEventListener, AbstractViewListene highlights_debounce_time = FEATURES_TIMEOUT code_lenses_debounce_time = FEATURES_TIMEOUT - _uri = None # type: str - @classmethod def applies_to_primary_view_only(cls) -> bool: return False @@ -152,6 +150,7 @@ def on_change() -> None: if this is not None: this._on_settings_object_changed() + self._uri = '' # assumed to never be falsey self._current_syntax = self.view.settings().get("syntax") existing_uri = view.settings().get("lsp_uri") if isinstance(existing_uri, str): diff --git a/plugin/hover.py b/plugin/hover.py index 40ae7337f..15ca25edc 100644 --- a/plugin/hover.py +++ b/plugin/hover.py @@ -12,6 +12,7 @@ from .core.protocol import TextDocumentPositionParams from .core.registry import LspTextCommand from .core.registry import windows +from .core.sessions import AbstractViewListener from .core.sessions import Session from .core.sessions import SessionBufferProtocol from .core.settings import userprefs @@ -28,7 +29,6 @@ from .core.views import text_document_range_params from .core.views import unpack_href_location from .core.views import update_lsp_popup -from .core.windows import AbstractViewListener from urllib.parse import unquote, urlparse import functools import re diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 4ba60a65c..6ba5b2db8 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -5,6 +5,7 @@ from .core.protocol import Request from .core.protocol import TextDocumentSyncKindFull from .core.protocol import TextDocumentSyncKindNone +from .core.sessions import Session from .core.sessions import SessionViewProtocol from .core.settings import userprefs from .core.types import Capabilities @@ -68,9 +69,9 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self.opened = False # Every SessionBuffer has its own personal capabilities due to "dynamic registration". self.capabilities = Capabilities() - self.session = session_view.session - self.session_views = WeakSet() # type: WeakSet[SessionViewProtocol] - self.session_views.add(session_view) + self._session = session_view.session + self._session_views = WeakSet() # type: WeakSet[SessionViewProtocol] + self._session_views.add(session_view) self.last_known_uri = uri self.id = buffer_id self.pending_changes = None # type: Optional[PendingChanges] @@ -86,7 +87,7 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self.diagnostics_debouncer = Debouncer() self.color_phantoms = sublime.PhantomSet(view, "lsp_color") self._check_did_open(view) - self.session.register_session_buffer_async(self) + self._session.register_session_buffer_async(self) def __del__(self) -> None: mgr = self.session.manager() @@ -100,6 +101,14 @@ def __del__(self) -> None: self._check_did_close() self.session.unregister_session_buffer_async(self) + @property + def session(self) -> Session: + return self._session + + @property + def session_views(self) -> 'WeakSet[SessionViewProtocol]': + return self._session_views + def _check_did_open(self, view: sublime.View) -> None: if not self.opened and self.should_notify_did_open(): language_id = self.get_language_id() diff --git a/plugin/session_view.py b/plugin/session_view.py index 4d8a5ff9a..b0597f811 100644 --- a/plugin/session_view.py +++ b/plugin/session_view.py @@ -6,13 +6,13 @@ from .core.protocol import DocumentUri from .core.protocol import Notification from .core.protocol import Request +from .core.sessions import AbstractViewListener from .core.sessions import Session from .core.settings import userprefs from .core.types import debounced from .core.typing import Any, Iterable, List, Tuple, Optional, Dict, Generator from .core.views import DIAGNOSTIC_SEVERITY from .core.views import text_document_identifier -from .core.windows import AbstractViewListener from .session_buffer import SessionBuffer from weakref import ref from weakref import WeakValueDictionary @@ -37,14 +37,14 @@ class SessionView: _session_buffers = WeakValueDictionary() # type: WeakValueDictionary[Tuple[int, int], SessionBuffer] def __init__(self, listener: AbstractViewListener, session: Session, uri: DocumentUri) -> None: - self.view = listener.view - self.session = session + self._view = listener.view + self._session = session self.active_requests = {} # type: Dict[int, Request] - self.listener = ref(listener) + self._listener = ref(listener) self.progress = {} # type: Dict[int, ViewProgressReporter] - self._code_lenses = CodeLensView(self.view) - settings = self.view.settings() - buffer_id = self.view.buffer_id() + self._code_lenses = CodeLensView(self._view) + settings = self._view.settings() + buffer_id = self._view.buffer_id() key = (id(session), buffer_id) session_buffer = self._session_buffers.get(key) if session_buffer is None: @@ -52,10 +52,10 @@ def __init__(self, listener: AbstractViewListener, session: Session, uri: Docume self._session_buffers[key] = session_buffer else: session_buffer.add_session_view(self) - self.session_buffer = session_buffer + self._session_buffer = session_buffer session.register_session_view_async(self) - session.config.set_view_status(self.view, "") - if self.session.has_capability(self.HOVER_PROVIDER_KEY): + session.config.set_view_status(self._view, "") + if self._session.has_capability(self.HOVER_PROVIDER_KEY): self._increment_hover_count() self._clear_auto_complete_triggers(settings) self._setup_auto_complete_triggers(settings) @@ -79,6 +79,22 @@ def on_before_remove(self) -> None: self.view.erase_regions(self.diagnostics_key(severity, True)) self.session_buffer.remove_session_view(self) + @property + def session(self) -> Session: + return self._session + + @property + def view(self) -> sublime.View: + return self._view + + @property + def listener(self) -> 'ref[AbstractViewListener]': + return self._listener + + @property + def session_buffer(self) -> SessionBuffer: + return self._session_buffer + def _is_listener_alive(self) -> bool: return bool(self.listener()) diff --git a/plugin/tooling.py b/plugin/tooling.py index d1ae7270c..7db306506 100644 --- a/plugin/tooling.py +++ b/plugin/tooling.py @@ -7,12 +7,13 @@ from .core.transports import TransportCallbacks from .core.types import Capabilities from .core.types import ClientConfig -from .core.typing import Any, Callable, Dict, List, Optional, Tuple +from .core.typing import Any, Callable, cast, Dict, List, Optional, Tuple from .core.version import __version__ from .core.views import extract_variables from .core.views import make_command_link from .core.workspace import ProjectFolders from .core.workspace import sorted_workspace_folders +from .session_buffer import SessionBuffer from base64 import b64decode from base64 import b64encode from subprocess import list2cmdline @@ -462,7 +463,7 @@ def print_capabilities(capabilities: Capabilities) -> str: p("## Global capabilities\n") p(print_capabilities(sv.session.capabilities) + "\n") p("## View-specific capabilities\n") - p(print_capabilities(sv.session_buffer.capabilities) + "\n") + p(print_capabilities(cast(SessionBuffer, sv.session_buffer).capabilities) + "\n") class ServerTestRunner(TransportCallbacks):