Repository: sublimelsp/LSP

Issue:
Wrong underline color in case of multiple diagnostics with different severities
**Describe the bug**
Diagnostics underline color doesn't use the highest severity if there are multiple diagnostics with different severity

**To Reproduce**
Steps to reproduce the behavior:
1. Install LSP-pyright (for example)
2. Create and open a Python file
3. Type `im`
4. Observe that there are two diagnostics, one "error" and one "warning", but the squiggly underline is orange

**Screenshots**

![severity](https://user-images.githubusercontent.com/6579999/198683905-fe254d2c-5d0f-470c-86fc-21a61a292a2c.png)


**Additional context**
I just wanted to log this issue here that I incidentally noticed, and I don't remember it being discussed before. But unfortunately I think it is impossible to solve (*edit*: possible workaround see below). The reason lies in ST's behavior for the `add_regions` draw order, which is different for icons and for inline decorations (underline). See here:

https://github.com/sublimelsp/LSP/blob/deeb0cbafce3deba4a37af47f9629b52179f8075/plugin/session_view.py#L107-L115

and

https://github.com/sublimelsp/LSP/blob/deeb0cbafce3deba4a37af47f9629b52179f8075/plugin/session_view.py#L130

If we reverse the range, then the underline would have the correct color (highest severity), but the icon would be the one with the lowest severity. So I think it's probably better to have the correct icon and potentially wrong underline color.

---

*Edit*: Perhaps a workaround could be to create two region types for each diagnostic severity, one for the icon and one for the underline, and initialize the icon regions in reversed order. But not sure whether the additional code needed for that is worth it.


Discussion:
As far as I remember it's the same with region "annotations". Those need to be added in opposite order to make the most severe appear on top.

Resolving patch:
diff --git a/plugin/session_view.py b/plugin/session_view.py
--- a/plugin/session_view.py
+++ b/plugin/session_view.py
@@ -80,8 +80,10 @@ def on_before_remove(self) -> None:
             self.session.unregister_session_view_async(self)
         self.session.config.erase_view_status(self.view)
         for severity in reversed(range(1, len(DIAGNOSTIC_SEVERITY) + 1)):
-            self.view.erase_regions(self.diagnostics_key(severity, False))
-            self.view.erase_regions(self.diagnostics_key(severity, True))
+            self.view.erase_regions("{}_icon".format(self.diagnostics_key(severity, False)))
+            self.view.erase_regions("{}_underline".format(self.diagnostics_key(severity, False)))
+            self.view.erase_regions("{}_icon".format(self.diagnostics_key(severity, True)))
+            self.view.erase_regions("{}_underline".format(self.diagnostics_key(severity, True)))
         self.view.erase_regions("lsp_document_link")
         self.session_buffer.remove_session_view(self)
 
@@ -134,7 +136,10 @@ def _initialize_region_keys(self) -> None:
         self.view.add_regions("lsp_document_link", r)
         for severity in range(1, 5):
             for mode in line_modes:
-                self.view.add_regions("lsp{}d{}{}".format(self.session.config.name, mode, severity), r)
+                self.view.add_regions("lsp{}d{}{}_icon".format(self.session.config.name, mode, severity), r)
+        for severity in range(4, 0, -1):
+            for mode in line_modes:
+                self.view.add_regions("lsp{}d{}{}_underline".format(self.session.config.name, mode, severity), r)
         if document_highlight_style in ("underline", "stippled"):
             for kind in document_highlight_kinds:
                 for mode in line_modes:
@@ -277,6 +282,7 @@ def present_diagnostics_async(self) -> None:
             listener.on_diagnostics_updated_async()
 
     def _draw_diagnostics(self, severity: int, max_severity_level: int, flags: int, multiline: bool) -> None:
+        ICON_FLAGS = sublime.HIDE_ON_MINIMAP | sublime.DRAW_NO_FILL | sublime.DRAW_NO_OUTLINE
         key = self.diagnostics_key(severity, multiline)
         key_tags = {tag: '{}_tags_{}'.format(key, tag) for tag in DIAGNOSTIC_TAG_VALUES}
         for key_tag in key_tags.values():
@@ -291,9 +297,11 @@ def _draw_diagnostics(self, severity: int, max_severity_level: int, flags: int,
                     self.view.add_regions(key_tags[tag], regions, tag_scope, flags=sublime.DRAW_NO_OUTLINE)
                 else:
                     non_tag_regions.extend(regions)
-            self.view.add_regions(key, non_tag_regions, data.scope, data.icon, flags)
+            self.view.add_regions("{}_icon".format(key), non_tag_regions, data.scope, data.icon, ICON_FLAGS)
+            self.view.add_regions("{}_underline".format(key), non_tag_regions, data.scope, "", flags)
         else:
-            self.view.erase_regions(key)
+            self.view.erase_regions("{}_icon".format(key))
+            self.view.erase_regions("{}_underline".format(key))
 
     def on_request_started_async(self, request_id: int, request: Request) -> None:
         self.active_requests[request_id] = request


Test patch:
diff --git a/tests/test_server_notifications.py b/tests/test_server_notifications.py
--- a/tests/test_server_notifications.py
+++ b/tests/test_server_notifications.py
@@ -36,19 +36,19 @@ def test_publish_diagnostics(self) -> Generator:
             ]
         }  # type: PublishDiagnosticsParams
         yield from self.await_client_notification("textDocument/publishDiagnostics", params)
-        yield lambda: len(self.view.get_regions("lspTESTds1")) > 0
-        yield lambda: len(self.view.get_regions("lspTESTds2")) > 0
-        yield lambda: len(self.view.get_regions("lspTESTds3")) > 0
+        errors_icon_regions = self.view.get_regions("lspTESTds1_icon")
+        errors_underline_regions = self.view.get_regions("lspTESTds1_underline")
+        warnings_icon_regions = self.view.get_regions("lspTESTds2_icon")
+        warnings_underline_regions = self.view.get_regions("lspTESTds2_underline")
+        info_icon_regions = self.view.get_regions("lspTESTds3_icon")
+        info_underline_regions = self.view.get_regions("lspTESTds3_underline")
+        yield lambda: len(errors_icon_regions) == len(errors_underline_regions) == 1
+        yield lambda: len(warnings_icon_regions) == len(warnings_underline_regions) == 1
+        yield lambda: len(info_icon_regions) == len(info_underline_regions) == 1
         yield lambda: len(self.view.get_regions("lspTESTds3_tags")) == 0
-        errors = self.view.get_regions("lspTESTds1")
-        warnings = self.view.get_regions("lspTESTds2")
-        info = self.view.get_regions("lspTESTds3")
-        self.assertEqual(len(errors), 1)
-        self.assertEqual(errors[0], sublime.Region(0, 1))
-        self.assertEqual(len(warnings), 1)
-        self.assertEqual(warnings[0], sublime.Region(2, 3))
-        self.assertEqual(len(info), 1)
-        self.assertEqual(info[0], sublime.Region(4, 5))
+        self.assertEqual(errors_underline_regions[0], sublime.Region(0, 1))
+        self.assertEqual(warnings_underline_regions[0], sublime.Region(2, 3))
+        self.assertEqual(info_underline_regions[0], sublime.Region(4, 5))
 
         # Testing whether the cursor position moves along with lsp_next_diagnostic