mirror of
https://github.com/HKUDS/OpenSpace.git
synced 2026-10-07 02:57:50 +00:00
148 lines
5.1 KiB
Python
148 lines
5.1 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
from collections import OrderedDict
|
|
from dataclasses import dataclass
|
|
from uuid import uuid4
|
|
|
|
from openspace.services.lsp.types import DiagnosticFile
|
|
|
|
MAX_DIAGNOSTICS_PER_FILE = 10
|
|
MAX_TOTAL_DIAGNOSTICS = 30
|
|
MAX_DELIVERED_FILES = 500
|
|
|
|
|
|
@dataclass(slots=True)
|
|
class PendingLSPDiagnostic:
|
|
server_name: str
|
|
files: list[DiagnosticFile]
|
|
timestamp: float
|
|
attachment_sent: bool = False
|
|
|
|
|
|
class LSPDiagnosticRegistry:
|
|
"""OpenSpace ``LSPDiagnosticRegistry.ts`` equivalent for passive diagnostics."""
|
|
|
|
def __init__(self) -> None:
|
|
self._pending: dict[str, PendingLSPDiagnostic] = {}
|
|
self._delivered: OrderedDict[str, set[str]] = OrderedDict()
|
|
|
|
def register_pending_lsp_diagnostic(self, *, server_name: str, files: list[DiagnosticFile]) -> None:
|
|
self._pending[str(uuid4())] = PendingLSPDiagnostic(
|
|
server_name=server_name,
|
|
files=files,
|
|
timestamp=time.time(),
|
|
)
|
|
|
|
def check_for_lsp_diagnostics(self) -> list[dict[str, object]]:
|
|
all_files: list[DiagnosticFile] = []
|
|
server_names: set[str] = set()
|
|
to_mark: list[PendingLSPDiagnostic] = []
|
|
for diagnostic in self._pending.values():
|
|
if not diagnostic.attachment_sent:
|
|
all_files.extend(diagnostic.files)
|
|
server_names.add(diagnostic.server_name)
|
|
to_mark.append(diagnostic)
|
|
if not all_files:
|
|
return []
|
|
|
|
deduped = self._deduplicate_files(all_files)
|
|
for diagnostic in to_mark:
|
|
diagnostic.attachment_sent = True
|
|
self._pending = {
|
|
key: value
|
|
for key, value in self._pending.items()
|
|
if not value.attachment_sent
|
|
}
|
|
|
|
total = 0
|
|
limited: list[DiagnosticFile] = []
|
|
severity_order = {"Error": 1, "Warning": 2, "Info": 3, "Hint": 4}
|
|
for file in deduped:
|
|
file.diagnostics.sort(key=lambda d: severity_order.get(d.severity, 4))
|
|
remaining = MAX_TOTAL_DIAGNOSTICS - total
|
|
if remaining <= 0:
|
|
break
|
|
file.diagnostics = file.diagnostics[: min(MAX_DIAGNOSTICS_PER_FILE, remaining)]
|
|
if file.diagnostics:
|
|
limited.append(file)
|
|
total += len(file.diagnostics)
|
|
|
|
for file in limited:
|
|
delivered = self._delivered.setdefault(file.uri, set())
|
|
self._delivered.move_to_end(file.uri)
|
|
for diagnostic in file.diagnostics:
|
|
delivered.add(self._diagnostic_key(diagnostic.to_json()))
|
|
while len(self._delivered) > MAX_DELIVERED_FILES:
|
|
self._delivered.popitem(last=False)
|
|
|
|
if total == 0:
|
|
return []
|
|
return [{"serverName": ", ".join(sorted(server_names)), "files": limited}]
|
|
|
|
def clear_all_lsp_diagnostics(self) -> None:
|
|
self._pending.clear()
|
|
|
|
def reset_all_lsp_diagnostic_state(self) -> None:
|
|
self._pending.clear()
|
|
self._delivered.clear()
|
|
|
|
def clear_delivered_diagnostics_for_file(self, file_uri: str) -> None:
|
|
self._delivered.pop(file_uri, None)
|
|
|
|
def pending_count(self) -> int:
|
|
return len(self._pending)
|
|
|
|
def _deduplicate_files(self, files: list[DiagnosticFile]) -> list[DiagnosticFile]:
|
|
seen_by_file: dict[str, set[str]] = {}
|
|
merged: dict[str, DiagnosticFile] = {}
|
|
for file in files:
|
|
seen = seen_by_file.setdefault(file.uri, set())
|
|
previously = self._delivered.get(file.uri, set())
|
|
target = merged.setdefault(file.uri, DiagnosticFile(uri=file.uri, diagnostics=[]))
|
|
for diagnostic in file.diagnostics:
|
|
key = self._diagnostic_key(diagnostic.to_json())
|
|
if key in seen or key in previously:
|
|
continue
|
|
seen.add(key)
|
|
target.diagnostics.append(diagnostic)
|
|
return [file for file in merged.values() if file.diagnostics]
|
|
|
|
@staticmethod
|
|
def _diagnostic_key(diagnostic: dict[str, object]) -> str:
|
|
return json.dumps(
|
|
{
|
|
"message": diagnostic.get("message"),
|
|
"severity": diagnostic.get("severity"),
|
|
"range": diagnostic.get("range"),
|
|
"source": diagnostic.get("source"),
|
|
"code": diagnostic.get("code"),
|
|
},
|
|
sort_keys=True,
|
|
ensure_ascii=False,
|
|
default=str,
|
|
)
|
|
|
|
|
|
diagnostic_registry = LSPDiagnosticRegistry()
|
|
|
|
|
|
def register_pending_lsp_diagnostic(*, server_name: str, files: list[DiagnosticFile]) -> None:
|
|
diagnostic_registry.register_pending_lsp_diagnostic(server_name=server_name, files=files)
|
|
|
|
|
|
def check_for_lsp_diagnostics() -> list[dict[str, object]]:
|
|
return diagnostic_registry.check_for_lsp_diagnostics()
|
|
|
|
|
|
def clear_all_lsp_diagnostics() -> None:
|
|
diagnostic_registry.clear_all_lsp_diagnostics()
|
|
|
|
|
|
def reset_all_lsp_diagnostic_state() -> None:
|
|
diagnostic_registry.reset_all_lsp_diagnostic_state()
|
|
|
|
|
|
def clear_delivered_diagnostics_for_file(file_uri: str) -> None:
|
|
diagnostic_registry.clear_delivered_diagnostics_for_file(file_uri)
|