Repository: sublimelsp/LSP

Issue:
Logging panel is too short
I am trying to debug an issue with `rust-analyzer`. According to [the RA docs](https://rust-analyzer.github.io/manual.html#configuration) I can get some useful information out of it by increasing logging. When I do that, by the time I can open the LSP log panel, the relevant messages are gone.

**To Reproduce**
Steps to reproduce the behavior:
1. Go to Preferences > Package Settings > LSP > Settings.
2. Add:

    ```json
    {
        "clients":
        {

            "rust-analyzer":
            {
                "command": ["~/.cargo/bin/rustup", "run", "nightly", "rust-analyzer"],
                "enabled": true,
                "env": {
                    "RA_LOG": "rust_analyzer=info",
                }
            },
        },
        "log_max_size": 0,
    }
    ```
    (Note that the `command` will probably have to change depending on how you installed RA.)

3. Restart the language server, or open a Rust project.
4. Open the LSP log panel.
5. Watch as the relevant messages disappear out of the top of the panel.

I am not just talking about scrolling here, scrolling the panel contents up will still hit this limit.

This information is not available via the `remote` log setting.

**Expected behavior**
I would expect to be able to set this somewhere.

**Screenshots**

Here's a GIF! I did not want to capture my actual code, but shortly after the GIF starts, I am restarting the RA language server.

![Peek 2021-07-26 13-08](https://user-images.githubusercontent.com/311239/126936375-7227dfeb-0360-49d8-a674-2a5612517cad.gif)


**Environment:**
- OS: Ubuntu 21.04
- Sublime Text version: 4113
- LSP version: 1.7.0
- Language servers used: `rust-analyzer`




Discussion:
There is the `log_max_size` setting that you can increase.

Though maybe if the server has so much output, it would be best to also support logging to file.
I could be wrong, but I think that setting is for line length, not total number of lines. At any rate, setting it to zero did not change this.
Found the cause.

There is a hard-coded value.
https://github.com/sublimelsp/LSP/blob/fe36b2d2c05126e9469fa3882dbff6731ce4a6b8/plugin/core/panels.py#L8

The content of the panel is deleted with this code:
https://github.com/sublimelsp/LSP/blob/fe36b2d2c05126e9469fa3882dbff6731ce4a6b8/plugin/core/panels.py#L191-L192

IMO not sure if the delete code is necessary.

Resolving patch:
diff --git a/boot.py b/boot.py
--- a/boot.py
+++ b/boot.py
@@ -18,6 +18,7 @@
 from .plugin.core.panels import destroy_output_panels
 from .plugin.core.panels import LspClearLogPanelCommand
 from .plugin.core.panels import LspClearPanelCommand
+from .plugin.core.panels import LspToggleLogPanelLinesLimitCommand
 from .plugin.core.panels import LspUpdatePanelCommand
 from .plugin.core.panels import LspUpdateLogPanelCommand
 from .plugin.core.panels import WindowPanelListener
diff --git a/plugin/core/panels.py b/plugin/core/panels.py
--- a/plugin/core/panels.py
+++ b/plugin/core/panels.py
@@ -4,9 +4,6 @@
 import sublime_plugin
 
 
-# about 80 chars per line implies maintaining a buffer of about 40kb per window
-LOG_PANEL_MAX_LINES = 500
-
 OUTPUT_PANEL_SETTINGS = {
     "auto_indent": False,
     "draw_indent_guides": False,
@@ -114,16 +111,47 @@ def create_panel(window: sublime.Window, name: str, result_file_regex: str, resu
     return panel
 
 
+def get_panel(window: Optional[sublime.Window], panel_name: str) -> Optional[sublime.View]:
+    if not window:
+        return None
+    return window.find_output_panel(panel_name)
+
+
 def is_panel_open(window: sublime.Window, panel_name: str) -> bool:
     return window.is_valid() and window.active_panel() == "output.{}".format(panel_name)
 
 
 def ensure_panel(window: sublime.Window, name: str, result_file_regex: str, result_line_regex: str,
                  syntax: str, context_menu: Optional[str] = None) -> Optional[sublime.View]:
-    return window.find_output_panel(name) or \
+    return get_panel(window, name) or \
         create_panel(window, name, result_file_regex, result_line_regex, syntax, context_menu)
 
 
+class LspToggleLogPanelLinesLimitCommand(sublime_plugin.TextCommand):
+    SETTING_NAME = 'lsp_limit_lines'
+    MAX_LINES_LIMIT_ON = 500
+    MAX_LINES_LIMIT_OFF = 10000
+
+    @classmethod
+    def is_limit_enabled(cls, window: Optional[sublime.Window]) -> bool:
+        panel = get_panel(window, PanelName.Log)
+        return bool(panel and panel.settings().get(cls.SETTING_NAME, True))
+
+    @classmethod
+    def get_lines_limit(cls, window: Optional[sublime.Window]) -> int:
+        return cls.MAX_LINES_LIMIT_ON if cls.is_limit_enabled(window) else cls.MAX_LINES_LIMIT_OFF
+
+    def run(self, edit: sublime.Edit) -> None:
+        window = self.view.window()
+        panel = get_panel(window, PanelName.Log)
+        if panel:
+            settings = panel.settings()
+            settings.set(self.SETTING_NAME, not self.is_limit_enabled(window))
+
+    def is_checked(self) -> bool:
+        return self.is_limit_enabled(self.view.window())
+
+
 class LspClearPanelCommand(sublime_plugin.TextCommand):
     """
     A clear_panel command to clear the error panel.
@@ -162,9 +190,10 @@ def log_server_message(window: sublime.Window, prefix: str, message: str) -> Non
         return
     WindowPanelListener.server_log_map[window_id].append((prefix, message))
     list_len = len(WindowPanelListener.server_log_map[window_id])
-    if list_len >= LOG_PANEL_MAX_LINES:
+    max_lines = LspToggleLogPanelLinesLimitCommand.get_lines_limit(window)
+    if list_len >= max_lines:
         # Trim leading items in the list, leaving only the max allowed count.
-        del WindowPanelListener.server_log_map[window_id][:list_len - LOG_PANEL_MAX_LINES]
+        del WindowPanelListener.server_log_map[window_id][:list_len - max_lines]
     panel = ensure_log_panel(window)
     if is_panel_open(window, PanelName.Log) and panel:
         update_log_panel(panel, window_id)
@@ -188,7 +217,8 @@ def run(self, edit: sublime.Edit, window_id: int) -> None:
                 self.view.insert(edit, self.view.size(), ''.join(new_lines))
                 last_region_end = 0  # Starting from point 0 in the panel ...
                 total_lines, _ = self.view.rowcol(self.view.size())
-                for _ in range(0, max(0, total_lines - LOG_PANEL_MAX_LINES)):
+                max_lines = LspToggleLogPanelLinesLimitCommand.get_lines_limit(self.view.window())
+                for _ in range(0, max(0, total_lines - max_lines)):
                     # ... collect all regions that span an entire line ...
                     region = self.view.full_line(last_region_end)
                     last_region_end = region.b


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,6 +1,6 @@
 from LSP.plugin.core.panels import ensure_log_panel
-from LSP.plugin.core.panels import LOG_PANEL_MAX_LINES
 from LSP.plugin.core.panels import log_server_message
+from LSP.plugin.core.panels import LspToggleLogPanelLinesLimitCommand
 from unittesting import DeferrableTestCase
 import sublime
 
@@ -29,7 +29,7 @@ def update_panel(self, msg: str) -> None:
         log_server_message(self.window, "test", msg)
 
     def test_server_panel_circular_behavior(self):
-        n = LOG_PANEL_MAX_LINES
+        n = LspToggleLogPanelLinesLimitCommand.MAX_LINES_LIMIT_ON
         for i in range(0, n + 1):
             self.update_panel(str(i))
         self.update_panel("overflow")