Repository: sublimelsp/LSP

Issue:
Diagnostics panel flickers constantly
When the diagnostics panel is open while you're typing, it appears to redraw multiple times per change, which a) is very distracting, and b) presumably isn't intentional.

Here's what happens when I type a single character:

![lsp-diag-flicker](https://user-images.githubusercontent.com/21655/30236771-3e099b52-9565-11e7-8ca8-ff2ac9bfbf32.gif)

This is redrawing a **lot** considering there was a single character changed. Viewing the GIF frame-by-frame shows the redraws (this was recorded at 60fps).

Not sure if this is a problem with the language server I'm using ([PHP](https://github.com/felixfbecker/php-language-server)) or with the plugin, but I suspect it's the plugin. Potentially a race condition? (Haven't debugged it yet.)


Discussion:
Debouncing the update would help somewhat here (VS Code seems to use `"editor.quickSuggestionsDelay": 10`) but I think there's also an issue with the redrawing generally.
This is especially disturbing when the error surpasses a screenful. The whole view starts scrolling a lot
Could you enable `log_debug` in your settings and share the results of a single keypress? A single diagnostics update should result in 1 update of the panel, but the way we write lines to the panel may not be batched in an efficient manner.

```
LSP: notify: textDocument/didSave
LSP: notify: textDocument/didSave
LSP: got json:  {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"diagnostics":[{"range":{"end":{"character":10,"line":88},"start":{"character":2,"line":78}},"message":"Warning 34: unused type ac
LSP: got json:  {"jsonrpc":"2.0","method":"textDocument/publishDiagnostics","params":{"diagnostics":[{"range":{"end":{"character":10,"line":88},"start":{"character":2,"line":78}},"message":"Warning 34: unused type ac
```
Two here
Each time a new diagnostics response is received, the panel's content is cleared and rebuilt from scratch using "view.insert()" for each message. This causes flickering, if many items are added. 

To solve this issue, all responses must be converted to the text content in the background and then be used to replace the whole panal's content with one "view.replace()" call.

EDIT: Maybe the scoll position should even be restored after update rather than scrolling to the bottom.
@deathaxe that sounds like a great fix!
@chenglou Two didSave events! That's not good - are you running some format-on-save logic (eg. another plugin?)?
No format on save
I'm only seeing the one `textDocument/didChange` event here, but that's still causing the flickering. I'll look at the `view.replace()` fix and see if that helps.

Resolving patch:
diff --git a/main.py b/main.py
--- a/main.py
+++ b/main.py
@@ -1285,6 +1285,20 @@ def run(self, edit):
         self.view.erase(edit, sublime.Region(0, self.view.size()))
 
 
+class LspUpdatePanelCommand(sublime_plugin.TextCommand):
+    """
+    A update_panel command to update the error panel with new text.
+    """
+
+    def run(self, edit, characters):
+        self.view.replace(edit, sublime.Region(0, self.view.size()), characters)
+
+        # Move cursor to the end
+        selection = self.view.sel()
+        selection.clear()
+        selection.add(sublime.Region(self.view.size(), self.view.size()))
+
+
 UNDERLINE_FLAGS = (sublime.DRAW_SQUIGGLY_UNDERLINE
                    | sublime.DRAW_NO_OUTLINE
                    | sublime.DRAW_NO_FILL
@@ -1426,35 +1440,32 @@ def update_diagnostics_panel(window):
         is_active_panel = (active_panel == "output.diagnostics")
         panel.settings().set("result_base_dir", base_dir)
         panel.set_read_only(False)
-        panel.run_command("lsp_clear_panel")
         file_diagnostics = window_file_diagnostics[window.id()]
         if file_diagnostics:
+            to_render = []
             for file_path, source_diagnostics in file_diagnostics.items():
                 relative_file_path = os.path.relpath(file_path, base_dir) if base_dir else file_path
                 if source_diagnostics:
-                    append_diagnostics(panel, relative_file_path, source_diagnostics)
+                    to_render.append(format_diagnostics(relative_file_path, source_diagnostics))
+            panel.run_command("lsp_update_panel", {"characters": "\n".join(to_render)})
             if auto_show_diagnostics_panel and not active_panel:
                 window.run_command("show_panel",
                                    {"panel": "output.diagnostics"})
         else:
+            panel.run_command("lsp_clear_panel")
             if auto_show_diagnostics_panel and is_active_panel:
                 window.run_command("hide_panel",
                                    {"panel": "output.diagnostics"})
         panel.set_read_only(True)
 
 
-def append_diagnostics(panel, file_path, origin_diagnostics):
-    panel.run_command('append',
-                      {'characters':  " ◌ {}:\n".format(file_path),
-                       'force': True})
+def format_diagnostics(file_path, origin_diagnostics):
+    content = " ◌ {}:\n".format(file_path)
     for origin, diagnostics in origin_diagnostics.items():
         for diagnostic in diagnostics:
             item = format_diagnostic(diagnostic)
-            panel.run_command('append', {
-                'characters': item + "\n",
-                'force': True,
-                'scroll_to_end': True
-            })
+            content += item + "\n"
+    return content
 
 
 def start_client(window: sublime.Window, config: ClientConfig):


Test patch: