Repository: sublimelsp/LSP

Issue:
Don't send codeAction request for file previews
This client sends the `textDocument/codeAction` request even for file previews of selected items in the quick panel, which appears in case of multiple location results for "Goto Definition...", but without sending a `textDocument/didOpen` notification first. This may lead to a server crash, because apparently VSCode behaves different and servers might expect the VSCode behavior, for more context see https://github.com/julia-vscode/LanguageServer.jl/issues/951#issuecomment-858783505. Here is a video with the payload (and where I've fixed the server code to not crash as a workaround):

https://user-images.githubusercontent.com/6579999/121701934-b5159c00-cad1-11eb-86ec-1c2c6a972956.mp4

I don't know if this is regulated somewhere in the specs, or if the server should handle these requests even before `didOpen`. But regardless of that, I think it is neither necessary nor useful to send the code action request for the file previews. Instead, we should wait until the user confirms the selection with <kbd>Enter</kbd> and only send the code action request after `didOpen`.

**Environment (please complete the following information):**
- OS: Windows 10
- Sublime Text version: 4107
- LSP version: main branch
- Language servers used: Julia language server (LanguageServer.jl)


Discussion:

Resolving patch:
diff --git a/plugin/code_actions.py b/plugin/code_actions.py
--- a/plugin/code_actions.py
+++ b/plugin/code_actions.py
@@ -5,7 +5,6 @@
 from .core.protocol import Error
 from .core.protocol import Request
 from .core.registry import LspTextCommand
-from .core.registry import sessions_for_view
 from .core.registry import windows
 from .core.sessions import SessionBufferProtocol
 from .core.settings import userprefs
@@ -125,27 +124,32 @@ def _request_async(
         with collector:
             file_name = view.file_name()
             if file_name:
-                for session in sessions_for_view(view, 'codeActionProvider'):
-                    if on_save_actions:
-                        supported_kinds = session.get_capability('codeActionProvider.codeActionKinds')
-                        matching_kinds = get_matching_kinds(on_save_actions, supported_kinds or [])
-                        if matching_kinds:
-                            params = text_document_code_action_params(
-                                view, file_name, region, [], matching_kinds)
-                            request = Request.codeAction(params, view)
-                            session.send_request_async(
-                                request, *filtering_collector(session.config.name, matching_kinds, collector))
-                    else:
-                        diagnostics = []  # type: Sequence[Diagnostic]
-                        for sb, diags in session_buffer_diagnostics:
-                            if sb.session == session:
-                                diagnostics = diags
-                                break
-                        if only_with_diagnostics and not diagnostics:
+                listener = windows.listener_for_view(view)
+                if listener:
+                    for sv in listener.session_views_async():
+                        if not sv.has_capability_async('codeActionProvider'):
                             continue
-                        params = text_document_code_action_params(view, file_name, region, diagnostics)
-                        request = Request.codeAction(params, view)
-                        session.send_request_async(request, collector.create_collector(session.config.name))
+                        session = sv.session
+                        if on_save_actions:
+                            supported_kinds = session.get_capability('codeActionProvider.codeActionKinds')
+                            matching_kinds = get_matching_kinds(on_save_actions, supported_kinds or [])
+                            if matching_kinds:
+                                params = text_document_code_action_params(
+                                    view, file_name, region, [], matching_kinds)
+                                request = Request.codeAction(params, view)
+                                session.send_request_async(
+                                    request, *filtering_collector(session.config.name, matching_kinds, collector))
+                        else:
+                            diagnostics = []  # type: Sequence[Diagnostic]
+                            for sb, diags in session_buffer_diagnostics:
+                                if sb.session == session:
+                                    diagnostics = diags
+                                    break
+                            if only_with_diagnostics and not diagnostics:
+                                continue
+                            params = text_document_code_action_params(view, file_name, region, diagnostics)
+                            request = Request.codeAction(params, view)
+                            session.send_request_async(request, collector.create_collector(session.config.name))
         if use_cache:
             self._response_cache = (location_cache_key, collector)
 
diff --git a/plugin/core/registry.py b/plugin/core/registry.py
--- a/plugin/core/registry.py
+++ b/plugin/core/registry.py
@@ -7,16 +7,6 @@
 import sublime_plugin
 
 
-def sessions_for_view(view: sublime.View, capability: Optional[str] = None) -> Generator[Session, None, None]:
-    """
-    Returns all sessions for this view, optionally matching the capability path.
-    """
-    window = view.window()
-    if window:
-        manager = windows.lookup(window)
-        yield from manager.sessions(view, capability)
-
-
 def best_session(view: sublime.View, sessions: Iterable[Session], point: Optional[int] = None) -> Optional[Session]:
     if point is None:
         try:
@@ -97,8 +87,12 @@ def session_by_name(self, name: Optional[str] = None, capability_path: Optional[
                         return None
         return None
 
-    def sessions(self, capability: Optional[str] = None) -> Generator[Session, None, None]:
-        yield from sessions_for_view(self.view, capability)
+    def sessions(self, capability_path: Optional[str] = None) -> Generator[Session, None, None]:
+        listener = windows.listener_for_view(self.view)
+        if listener:
+            for sv in listener.session_views_async():
+                if capability_path is None or sv.has_capability_async(capability_path):
+                    yield sv.session
 
 
 class LspRestartClientCommand(sublime_plugin.TextCommand):


Test patch: