Repository: sublimelsp/LSP

Issue:
Debug output blocks UI
**Describe the bug**
When there is a lot of communication the UI may freeze up to a few seconds. This does happen if the output panel is closed too.

**To Reproduce**
Steps to reproduce the behavior:
1. Enable debugging  options
2. Generate a lot of communication (eg. LaTex build with LSP-TexLab)

**Expected behavior**
UI does not freeze (especially if the debug panel is closed).

**Environment (please complete the following information):**
- OS: Windows 10
- Sublime Text version: 4099
- LSP version: Commit 218c87d

**Additional context**
I am running a really powerful machine, I can image that the situation may be worse on a weaker machine.



Discussion:
Would be nice to not update the panel in the background as indeed it's not a very "cheap" thing to do.

And having it not update automatically would allow us to make the panel always available, without needing to enable the relevant option.

The only difficulty would be to enable the panel updates only when it's open. Not sure what would be the best way to detect that. Maybe `on_window_command` listener checking for `show_panel` command.

Resolving patch:
diff --git a/boot.py b/boot.py
--- a/boot.py
+++ b/boot.py
@@ -21,6 +21,7 @@
 from .plugin.core.panels import LspClearPanelCommand
 from .plugin.core.panels import LspUpdatePanelCommand
 from .plugin.core.panels import LspUpdateServerPanelCommand
+from .plugin.core.panels import WindowPanelListener
 from .plugin.core.protocol import Response
 from .plugin.core.protocol import WorkspaceFolder
 from .plugin.core.registry import LspRecheckSessionsCommand
diff --git a/plugin/core/panels.py b/plugin/core/panels.py
--- a/plugin/core/panels.py
+++ b/plugin/core/panels.py
@@ -1,5 +1,4 @@
 from .typing import Dict, Optional, List, Generator, Tuple
-from .types import debounced
 from contextlib import contextmanager
 import sublime
 import sublime_plugin
@@ -8,9 +7,6 @@
 # about 80 chars per line implies maintaining a buffer of about 40kb per window
 SERVER_PANEL_MAX_LINES = 500
 
-# If nothing else shows up after 80ms, actually print the messages to the panel
-SERVER_PANEL_DEBOUNCE_TIME_MS = 80
-
 OUTPUT_PANEL_SETTINGS = {
     "auto_indent": False,
     "draw_indent_guides": False,
@@ -46,6 +42,31 @@ def mutable(view: sublime.View) -> Generator:
     view.set_read_only(True)
 
 
+class WindowPanelListener(sublime_plugin.EventListener):
+
+    server_log_map = {}  # type: Dict[int, List[Tuple[str, str]]]
+
+    def on_init(self, views: List[sublime.View]) -> None:
+        for window in sublime.windows():
+            self.server_log_map[window.id()] = []
+
+    def on_new_window(self, window: sublime.Window) -> None:
+        self.server_log_map[window.id()] = []
+
+    def on_pre_close_window(self, window: sublime.Window) -> None:
+        self.server_log_map.pop(window.id())
+
+    def on_window_command(self, window: sublime.Window, command_name: str, args: Dict) -> None:
+        if command_name in ('show_panel', 'hide_panel'):
+            sublime.set_timeout(lambda: self.maybe_update_server_panel(window))
+
+    def maybe_update_server_panel(self, window: sublime.Window) -> None:
+        if is_server_panel_open(window):
+            panel = ensure_server_panel(window)
+            if panel:
+                update_server_panel(panel, window.id())
+
+
 def create_output_panel(window: sublime.Window, name: str) -> Optional[sublime.View]:
     panel = window.create_output_panel(name)
     settings = panel.settings()
@@ -118,44 +139,29 @@ def ensure_server_panel(window: sublime.Window) -> Optional[sublime.View]:
     return ensure_panel(window, PanelName.LanguageServers, "", "", "Packages/LSP/Syntaxes/ServerLog.sublime-syntax")
 
 
-def update_server_panel(window: sublime.Window, prefix: str, message: str) -> None:
+def is_server_panel_open(window: sublime.Window) -> bool:
+    return window.is_valid() and window.active_panel() == "output.{}".format(PanelName.LanguageServers)
+
+
+def log_server_message(window: sublime.Window, prefix: str, message: str) -> None:
     if not window.is_valid():
         return
     window_id = window.id()
+    WindowPanelListener.server_log_map[window_id].append((prefix, message))
     panel = ensure_server_panel(window)
-    if not panel:
-        return
-    LspUpdateServerPanelCommand.to_be_processed.setdefault(window_id, []).append((prefix, message))
-    previous_length = len(LspUpdateServerPanelCommand.to_be_processed[window_id])
-
-    def condition() -> bool:
-        if not panel:
-            return False
-        if not panel.is_valid():
-            return False
-        to_process = LspUpdateServerPanelCommand.to_be_processed.get(window_id)
-        if to_process is None:
-            return False
-        current_length = len(to_process)
-        if current_length >= 10:
-            # Do not let the queue grow large.
-            return True
-        # If the queue remains stable, flush the messages.
-        return current_length == previous_length
-
-    debounced(
-        lambda: panel.run_command("lsp_update_server_panel", {"window_id": window_id}) if panel else None,
-        SERVER_PANEL_DEBOUNCE_TIME_MS,
-        condition
-    )
+    if is_server_panel_open(window) and panel:
+        update_server_panel(panel, window_id)
 
 
-class LspUpdateServerPanelCommand(sublime_plugin.TextCommand):
+def update_server_panel(panel: sublime.View, window_id: int) -> None:
+    panel.run_command("lsp_update_server_panel", {"window_id": window_id})
 
-    to_be_processed = {}  # type: Dict[int, List[Tuple[str, str]]]
+
+class LspUpdateServerPanelCommand(sublime_plugin.TextCommand):
 
     def run(self, edit: sublime.Edit, window_id: int) -> None:
-        to_process = self.to_be_processed.pop(window_id)
+        to_process = WindowPanelListener.server_log_map.get(window_id) or []
+        WindowPanelListener.server_log_map[window_id] = []
         with mutable(self.view):
             for prefix, message in to_process:
                 message = message.replace("\r\n", "\n")  # normalize Windows eol
diff --git a/plugin/core/windows.py b/plugin/core/windows.py
--- a/plugin/core/windows.py
+++ b/plugin/core/windows.py
@@ -5,7 +5,7 @@
 from .logging import debug
 from .logging import exception_log
 from .message_request_handler import MessageRequestHandler
-from .panels import update_server_panel
+from .panels import log_server_message
 from .protocol import CodeLens, Diagnostic
 from .protocol import Error
 from .sessions import get_plugin
@@ -443,7 +443,7 @@ def plugin_unloaded(self) -> None:
         self._end_sessions_async()
 
     def handle_server_message(self, server_name: str, message: str) -> None:
-        sublime.set_timeout(lambda: update_server_panel(self._window, server_name, message))
+        sublime.set_timeout(lambda: log_server_message(self._window, server_name, message))
 
     def handle_log_message(self, session: Session, params: Any) -> None:
         self.handle_server_message(session.config.name, extract_message(params))


Test patch:
diff --git a/tests/test_server_panel_circular.py b/tests/test_server_panel_circular.py
--- a/tests/test_server_panel_circular.py
+++ b/tests/test_server_panel_circular.py
@@ -1,7 +1,6 @@
 from LSP.plugin.core.panels import ensure_server_panel
-from LSP.plugin.core.panels import SERVER_PANEL_DEBOUNCE_TIME_MS
 from LSP.plugin.core.panels import SERVER_PANEL_MAX_LINES
-from LSP.plugin.core.panels import update_server_panel
+from LSP.plugin.core.panels import log_server_message
 from unittesting import DeferrableTestCase
 import sublime
 
@@ -27,7 +26,7 @@ def assert_total_lines_equal(self, expected_total_lines):
         self.assertEqual(actual_total_lines, expected_total_lines)
 
     def update_panel(self, msg: str) -> None:
-        update_server_panel(self.window, "test", msg)
+        log_server_message(self.window, "test", msg)
 
     def test_server_panel_circular_behavior(self):
         n = SERVER_PANEL_MAX_LINES
@@ -36,5 +35,4 @@ def test_server_panel_circular_behavior(self):
         self.update_panel("overflow")
         self.update_panel("overflow")
         self.update_panel("one\ntwo\nthree")
-        yield SERVER_PANEL_DEBOUNCE_TIME_MS
         self.assert_total_lines_equal(n)