diff --git a/LSP.sublime-settings b/LSP.sublime-settings index ab90e0789..57a2e9a9a 100644 --- a/LSP.sublime-settings +++ b/LSP.sublime-settings @@ -293,6 +293,17 @@ // // // Extra variables to override/add to language server's environment. // "env": { }, + // + // // Sets the diagnostics mode: + // // "open_files" - All diagnostics reported from the server are shown. + // // If the server supports `diagnosticProvider`, diagnostics are + // // requested only for files which are opened in the editor and they + // // are cleared when the file gets closed. + // // "workspace" - If there are project folders (folders in the side bar), + // // diagnostics for files not within those folders are ignored. + // // If the server supports `diagnosticProvider.workspaceDiagnostics`, + // // diagnostics are requested for all files in the project folders. + // "diagnostics_mode": "open_files", // } // } "clients": {}, diff --git a/docs/src/client_configuration.md b/docs/src/client_configuration.md index 1945bfd13..db471c8e8 100644 --- a/docs/src/client_configuration.md +++ b/docs/src/client_configuration.md @@ -42,7 +42,7 @@ Below is an example of the `LSP.sublime-settings` file with configurations for t | initializationOptions | options to send to the server at startup (rarely used) | | selector | This is _the_ connection between your files and language servers. It's a selector that is matched against the current view's base scope. If the selector matches with the base scope of the the file, the associated language server is started. For more information, see https://www.sublimetext.com/docs/3/selectors.html | | priority_selector | Used to prioritize a certain language server when choosing which one to query on views with multiple servers active. Certain LSP actions have to pick which server to query and this setting can be used to decide which one to pick based on the current scopes at the cursor location. For example when having both HTML and PHP servers running on a PHP file, this can be used to give priority to the HTML one in HTML blocks and to PHP one otherwise. That would be done by setting "feature_selector" to `text.html` for HTML server and `source.php` to PHP server. Note: when the "feature_selector" is missing, it will be the same as the "document_selector". -| hide_non_project_diagnostics | Enable to ignore diagnostics for files that are not within the project (window) folders. If project has no folders then this option has no effect and diagnostics are shown for all files. | +| diagnostics_mode | Set to `"workspace"` (default is `"open_files"`) to ignore diagnostics for files that are not within the project (window) folders. If project has no folders then this option has no effect and diagnostics are shown for all files. If the server supports _pull diagnostics_ (`diagnosticProvider`), this setting also controls whether diagnostics are requested only for open files (`"open_files"`), or for all files in the project folders (`"workspace"`). | | tcp_port | see instructions below | | experimental_capabilities | Turn on experimental capabilities of a language server. This is a dictionary and differs per language server | | disabled_capabilities | Disables specific capabilities of a language server. This is a dictionary with key being a capability key and being `true`. Refer to the `ServerCapabilities` structure in [LSP capabilities](https://microsoft.github.io/language-server-protocol/specifications/specification-current/#initialize) to find capabilities that you might want to disable. Note that the value should be `true` rather than `false` for capabilites that you want to disable. For example: `"signatureHelpProvider": true` | diff --git a/messages/1.24.0.txt b/messages/1.24.0.txt index d1084be00..16bf0b164 100644 --- a/messages/1.24.0.txt +++ b/messages/1.24.0.txt @@ -5,5 +5,5 @@ Sublime Text once it finishes updating all packages. ⚠️ # Breaking changes -- Diagnostics for files that are not withing the project folders are no longer ignored. - You can set `hide_non_project_diagnostics` in server-specific configuration to enable old behavior. +- Diagnostics for files that are not within the project folders are no longer ignored. + You can set `"diagnostics_mode": "workspace"` in server-specific configuration to enable old behavior. diff --git a/plugin/core/protocol.py b/plugin/core/protocol.py index 561fa0113..210f5a9c2 100644 --- a/plugin/core/protocol.py +++ b/plugin/core/protocol.py @@ -5845,19 +5845,21 @@ class TokenFormat(Enum): class Request(Generic[R]): - __slots__ = ('method', 'params', 'view', 'progress') + __slots__ = ('method', 'params', 'view', 'progress', 'partial_results') def __init__( self, method: str, params: Any = None, view: Optional[sublime.View] = None, - progress: bool = False + progress: bool = False, + partial_results: bool = False ) -> None: self.method = method self.params = params self.view = view self.progress = progress # type: Union[bool, str] + self.partial_results = partial_results @classmethod def initialize(cls, params: InitializeParams) -> 'Request': @@ -5975,7 +5977,7 @@ def documentDiagnostic(cls, params: DocumentDiagnosticParams, view: sublime.View @classmethod def workspaceDiagnostic(cls, params: WorkspaceDiagnosticParams) -> 'Request': - return Request('workspace/diagnostic', params) + return Request('workspace/diagnostic', params, partial_results=True) @classmethod def shutdown(cls) -> 'Request': @@ -6124,6 +6126,13 @@ def to_lsp(self) -> 'Position': } +ResponseError = TypedDict('ResponseError', { + 'code': int, + 'message': str, + 'data': NotRequired['LSPAny'] +}) + + CodeLensExtended = TypedDict('CodeLensExtended', { # The range in which this code lens is valid. Should only span a single line. 'range': 'Range', diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index 48f348876..80f686225 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -24,10 +24,12 @@ from .protocol import CompletionItemKind from .protocol import CompletionItemTag from .protocol import Diagnostic +from .protocol import DiagnosticServerCancellationData from .protocol import DiagnosticSeverity from .protocol import DiagnosticTag from .protocol import DidChangeWatchedFilesRegistrationOptions from .protocol import DidChangeWorkspaceFoldersParams +from .protocol import DocumentDiagnosticReportKind from .protocol import DocumentLink from .protocol import DocumentUri from .protocol import Error @@ -43,15 +45,18 @@ from .protocol import Location from .protocol import LocationLink from .protocol import LSPAny +from .protocol import LSPErrorCodes from .protocol import LSPObject from .protocol import MarkupKind from .protocol import Notification from .protocol import PrepareSupportDefaultBehavior +from .protocol import PreviousResultId from .protocol import PublishDiagnosticsParams from .protocol import RegistrationParams from .protocol import Range from .protocol import Request from .protocol import Response +from .protocol import ResponseError from .protocol import SemanticTokenModifiers from .protocol import SemanticTokenTypes from .protocol import SymbolKind @@ -62,6 +67,10 @@ from .protocol import UnregistrationParams from .protocol import WindowClientCapabilities from .protocol import WorkspaceClientCapabilities +from .protocol import WorkspaceDiagnosticParams +from .protocol import WorkspaceDiagnosticReport +from .protocol import WorkspaceDocumentDiagnosticReport +from .protocol import WorkspaceFullDocumentDiagnosticReport from .protocol import WorkspaceEdit from .settings import client_configs from .settings import globalprefs @@ -76,9 +85,11 @@ from .types import method_to_capability from .types import SettingsRegistration from .types import sublime_pattern_to_glob -from .typing import Callable, cast, Dict, Any, Optional, List, Tuple, Generator, Type, Protocol, Mapping, Set, TypeVar, Union # noqa: E501 +from .types import WORKSPACE_DIAGNOSTICS_TIMEOUT +from .typing import Callable, cast, Dict, Any, Optional, List, Tuple, Generator, Type, TypeGuard, Protocol, Mapping, Set, TypeVar, Union # noqa: E501 from .url import filename_to_uri from .url import parse_uri +from .url import unparse_uri from .version import __version__ from .views import extract_variables from .views import get_storage_path @@ -101,6 +112,16 @@ T = TypeVar('T') +def is_workspace_full_document_diagnostic_report( + report: WorkspaceDocumentDiagnosticReport +) -> TypeGuard[WorkspaceFullDocumentDiagnosticReport]: + return report['kind'] == DocumentDiagnosticReportKind.Full + + +def is_diagnostic_server_cancellation_data(data: Any) -> TypeGuard[DiagnosticServerCancellationData]: + return isinstance(data, dict) and 'retriggerRequest' in data + + def get_semantic_tokens_map(custom_tokens_map: Optional[Dict[str, str]]) -> Tuple[Tuple[str, str], ...]: tokens_scope_map = SEMANTIC_TOKENS_MAP.copy() if custom_tokens_map is not None: @@ -550,6 +571,10 @@ def session(self) -> 'Session': def session_views(self) -> 'WeakSet[SessionViewProtocol]': ... + @property + def version(self) -> Optional[int]: + ... + def get_uri(self) -> Optional[str]: ... @@ -1162,8 +1187,9 @@ def check_applicable(self, sb: SessionBufferProtocol) -> None: return -# This prefix should disambiguate common string generation techniques like UUID4. -_WORK_DONE_PROGRESS_PREFIX = "$ublime-" +# These prefixes should disambiguate common string generation techniques like UUID4. +_WORK_DONE_PROGRESS_PREFIX = "$ublime-work-done-progress-" +_PARTIAL_RESULT_PROGRESS_PREFIX = "$ublime-partial-result-progress-" class Session(TransportCallbacks): @@ -1182,6 +1208,8 @@ def __init__(self, manager: Manager, logger: Logger, workspace_folders: List[Wor self.state = ClientStates.STARTING self.capabilities = Capabilities() self.diagnostics = DiagnosticsStorage() + self.diagnostics_result_ids = {} # type: Dict[DocumentUri, Optional[str]] + self.workspace_diagnostics_pending_response = None # type: Optional[int] self.exiting = False self._registrations = {} # type: Dict[str, _RegistrationData] self._init_callback = None # type: Optional[InitCallback] @@ -1467,6 +1495,9 @@ def _handle_initialize_success(self, result: InitializeResult) -> None: if self._init_callback: self._init_callback(self, False) self._init_callback = None + if self.config.diagnostics_mode == "workspace" and \ + self.has_capability('diagnosticProvider.workspaceDiagnostics'): + self.do_workspace_diagnostics_async() def _handle_initialize_error(self, result: InitializeError) -> None: self._initialize_error = (result.get('code', -1), Exception(result.get('message', 'Error initializing server'))) @@ -1695,6 +1726,76 @@ def session_views_by_visibility(self) -> Tuple[Set[SessionViewProtocol], Set[Ses not_visible_session_views.add(sv) return visible_session_views, not_visible_session_views + # --- Workspace Pull Diagnostics ----------------------------------------------------------------------------------- + + def do_workspace_diagnostics_async(self) -> None: + if self.workspace_diagnostics_pending_response: + # The server is probably leaving the request open intentionally, in order to continuously stream updates via + # $/progress notifications. + return + previous_result_ids = [ + {'uri': uri, 'value': result_id} for uri, result_id in self.diagnostics_result_ids.items() + if result_id is not None + ] # type: List[PreviousResultId] + params = {'previousResultIds': previous_result_ids} # type: WorkspaceDiagnosticParams + identifier = self.get_capability("diagnosticProvider.identifier") + if identifier: + params['identifier'] = identifier + self.workspace_diagnostics_pending_response = self.send_request_async( + Request.workspaceDiagnostic(params), + self._on_workspace_diagnostics_async, + self._on_workspace_diagnostics_error_async) + + def _on_workspace_diagnostics_async( + self, response: WorkspaceDiagnosticReport, reset_pending_response: bool = True + ) -> None: + if reset_pending_response: + self.workspace_diagnostics_pending_response = None + if not response['items']: + return + window = sublime.active_window() + active_view = window.active_view() if window else None + active_view_path = active_view.file_name() if active_view else None + for diagnostic_report in response['items']: + uri = diagnostic_report['uri'] + # Normalize URI + scheme, path = parse_uri(uri) + if scheme == 'file': + # Skip for active view + if path == active_view_path: + continue + uri = unparse_uri((scheme, path)) + # Note: 'version' is a mandatory field, but some language servers have serialization bugs with null values. + version = diagnostic_report.get('version') + # Skip if outdated + # Note: this is just a necessary, but not a sufficient condition to decide whether the diagnostics for this + # file are likely not accurate anymore, because changes in another file in the meanwhile could have affected + # the diagnostics in this file. If this is the case, a new request is already queued, or updated partial + # results are expected to be streamed by the server. + if isinstance(version, int): + sb = self.get_session_buffer_for_uri_async(uri) + if sb and sb.version != version: + continue + self.diagnostics_result_ids[uri] = diagnostic_report.get('resultId') + if is_workspace_full_document_diagnostic_report(diagnostic_report): + self.m_textDocument_publishDiagnostics({'uri': uri, 'diagnostics': diagnostic_report['items']}) + + def _on_workspace_diagnostics_error_async(self, error: ResponseError) -> None: + if error['code'] == LSPErrorCodes.ServerCancelled: + data = error.get('data') + if is_diagnostic_server_cancellation_data(data) and data['retriggerRequest']: + # Retrigger the request after a short delay, but don't reset the pending response variable for this + # moment, to prevent new requests of this type in the meanwhile. The delay is used in order to prevent + # infinite cycles of cancel -> retrigger, in case the server is busy. + + def _retrigger_request() -> None: + self.workspace_diagnostics_pending_response = None + self.do_workspace_diagnostics_async() + + sublime.set_timeout_async(_retrigger_request, WORKSPACE_DIAGNOSTICS_TIMEOUT) + return + self.workspace_diagnostics_pending_response = None + # --- server request handlers -------------------------------------------------------------------------------------- def m_window_showMessageRequest(self, params: Any, request_id: Any) -> None: @@ -1895,6 +1996,16 @@ def m___progress(self, params: Any) -> None: """handles the $/progress notification""" token = params['token'] value = params['value'] + # Partial Result Progress + # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#partialResults + if token.startswith(_PARTIAL_RESULT_PROGRESS_PREFIX): + request_id = int(token[len(_PARTIAL_RESULT_PROGRESS_PREFIX):]) + request = self._response_handlers[request_id][0] + if request.method == "workspace/diagnostic": + self._on_workspace_diagnostics_async(value, reset_pending_response=False) + return + # Work Done Progress + # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#workDoneProgress kind = value['kind'] if token not in self._progress: # If the token is not in the _progress map then that could mean two things: @@ -1996,6 +2107,8 @@ def send_request_async( request_id = self.request_id if request.progress and isinstance(request.params, dict): request.params["workDoneToken"] = _WORK_DONE_PROGRESS_PREFIX + str(request_id) + if request.partial_results and isinstance(request.params, dict): + request.params["partialResultToken"] = _PARTIAL_RESULT_PROGRESS_PREFIX + str(request_id) self._response_handlers[request_id] = (request, on_result, on_error) self._invoke_views(request, "on_request_started_async", request_id, request) if self._plugin: diff --git a/plugin/core/types.py b/plugin/core/types.py index c2b211a12..ffca06e8e 100644 --- a/plugin/core/types.py +++ b/plugin/core/types.py @@ -21,6 +21,7 @@ TCP_CONNECT_TIMEOUT = 5 # seconds FEATURES_TIMEOUT = 300 # milliseconds +WORKSPACE_DIAGNOSTICS_TIMEOUT = 3000 # milliseconds PANEL_FILE_REGEX = r"^(\S.*):$" PANEL_LINE_REGEX = r"^\s+(\d+):(\d+)" @@ -653,7 +654,7 @@ def __init__(self, disabled_capabilities: DottedDict = DottedDict(), file_watcher: FileWatcherConfig = {}, semantic_tokens: Optional[Dict[str, str]] = None, - hide_non_project_diagnostics: bool = False, + diagnostics_mode: str = "open_files", path_maps: Optional[List[PathMap]] = None) -> None: self.name = name self.selector = selector @@ -679,7 +680,7 @@ def __init__(self, self.path_maps = path_maps self.status_key = "lsp_{}".format(self.name) self.semantic_tokens = semantic_tokens - self.hide_non_project_diagnostics = hide_non_project_diagnostics + self.diagnostics_mode = diagnostics_mode @classmethod def from_sublime_settings(cls, name: str, s: sublime.Settings, file: str) -> "ClientConfig": @@ -712,7 +713,7 @@ def from_sublime_settings(cls, name: str, s: sublime.Settings, file: str) -> "Cl disabled_capabilities=disabled_capabilities, file_watcher=file_watcher, semantic_tokens=semantic_tokens, - hide_non_project_diagnostics=bool(s.get("hide_non_project_diagnostics", False)), + diagnostics_mode=str(s.get("diagnostics_mode", "open_files")), path_maps=PathMap.parse(s.get("path_maps")) ) @@ -742,7 +743,7 @@ def from_dict(cls, name: str, d: Dict[str, Any]) -> "ClientConfig": disabled_capabilities=disabled_capabilities, file_watcher=d.get("file_watcher", dict()), semantic_tokens=d.get("semantic_tokens", dict()), - hide_non_project_diagnostics=d.get("hide_non_project_diagnostics", False), + diagnostics_mode=d.get("diagnostics_mode", "open_files"), path_maps=PathMap.parse(d.get("path_maps")) ) @@ -772,8 +773,7 @@ def from_config(cls, src_config: "ClientConfig", override: Dict[str, Any]) -> "C disabled_capabilities=disabled_capabilities, file_watcher=override.get("file_watcher", src_config.file_watcher), semantic_tokens=override.get("semantic_tokens", src_config.semantic_tokens), - hide_non_project_diagnostics=override.get( - "hide_non_project_diagnostics", src_config.hide_non_project_diagnostics), + diagnostics_mode=override.get("diagnostics_mode", src_config.diagnostics_mode), path_maps=path_map_override if path_map_override else src_config.path_maps ) diff --git a/plugin/core/windows.py b/plugin/core/windows.py index 03335a0e9..e797bdd82 100644 --- a/plugin/core/windows.py +++ b/plugin/core/windows.py @@ -348,7 +348,7 @@ def should_ignore_diagnostics(self, uri: DocumentUri, configuration: ClientConfi scheme, path = parse_uri(uri) if scheme != "file": return None - if configuration.hide_non_project_diagnostics and not self._workspace.contains(path): + if configuration.diagnostics_mode == "workspace" and not self._workspace.contains(path): return "not inside window folders" view = self._window.active_view() if not view: diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index 49da9e673..3a2db3eaa 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -1,23 +1,23 @@ from .core.protocol import ColorInformation from .core.protocol import Diagnostic -from .core.protocol import DiagnosticServerCancellationData from .core.protocol import DiagnosticSeverity from .core.protocol import DocumentDiagnosticParams from .core.protocol import DocumentDiagnosticReport from .core.protocol import DocumentDiagnosticReportKind from .core.protocol import DocumentLink from .core.protocol import DocumentUri -from .core.protocol import Error from .core.protocol import FullDocumentDiagnosticReport from .core.protocol import InlayHint from .core.protocol import InlayHintParams from .core.protocol import LSPErrorCodes from .core.protocol import Request +from .core.protocol import ResponseError from .core.protocol import SemanticTokensDeltaParams from .core.protocol import SemanticTokensParams from .core.protocol import SemanticTokensRangeParams from .core.protocol import TextDocumentSaveReason from .core.protocol import TextDocumentSyncKind +from .core.sessions import is_diagnostic_server_cancellation_data from .core.sessions import Session from .core.sessions import SessionViewProtocol from .core.settings import userprefs @@ -25,6 +25,7 @@ from .core.types import debounced from .core.types import DebouncerNonThreadSafe from .core.types import FEATURES_TIMEOUT +from .core.types import WORKSPACE_DIAGNOSTICS_TIMEOUT from .core.typing import Any, Callable, Iterable, Optional, List, Protocol, Set, Dict, Tuple, TypeGuard, Union from .core.typing import cast from .core.views import DIAGNOSTIC_SEVERITY @@ -61,10 +62,6 @@ def __call__(self, *args: Any) -> None: ... -def is_diagnostic_server_cancellation_data(data: Any) -> TypeGuard[DiagnosticServerCancellationData]: - return isinstance(data, dict) and 'retriggerRequest' in data - - def is_full_document_diagnostic_report(response: DocumentDiagnosticReport) -> TypeGuard[FullDocumentDiagnosticReport]: return response['kind'] == DocumentDiagnosticReportKind.Full @@ -135,11 +132,11 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum self.diagnostics_version = -1 self.diagnostics_flags = 0 self.diagnostics_are_visible = False - self.document_diagnostic_result_id = None # type: Optional[str] self.document_diagnostic_needs_refresh = False self.document_diagnostic_pending_response = None # type: Optional[int] self.last_text_change_time = 0.0 self.diagnostics_debouncer_async = DebouncerNonThreadSafe(async_thread=True) + self.workspace_diagnostics_debouncer_async = DebouncerNonThreadSafe(async_thread=True) self.color_phantoms = sublime.PhantomSet(view, "lsp_color") self.document_links = [] # type: List[DocumentLink] self.semantic_tokens = SemanticTokensData() @@ -159,6 +156,11 @@ def session(self) -> Session: def session_views(self) -> 'WeakSet[SessionViewProtocol]': return self._session_views + @property + def version(self) -> Optional[int]: + view = self.some_view() + return view.change_count() if view else None + def _check_did_open(self, view: sublime.View) -> None: if not self.opened and self.should_notify_did_open(): language_id = self.get_language_id() @@ -216,7 +218,7 @@ def remove_session_view(self, sv: SessionViewProtocol) -> None: def _on_before_destroy(self) -> None: self.remove_all_inlay_hints() - if self.has_capability("diagnosticProvider"): + if self.has_capability("diagnosticProvider") and self.session.config.diagnostics_mode == "open_files": self.session.m_textDocument_publishDiagnostics({'uri': self.last_known_uri, 'diagnostics': []}) wm = self.session.manager() if wm: @@ -344,6 +346,11 @@ def _on_after_change_async(self, view: sublime.View, version: int) -> None: return self._do_color_boxes_async(view, version) self.do_document_diagnostic_async(view, version) + if self.session.config.diagnostics_mode == "workspace" and \ + not self.session.workspace_diagnostics_pending_response and \ + self.session.has_capability('diagnosticProvider.workspaceDiagnostics'): + self.workspace_diagnostics_debouncer_async.debounce( + self.session.do_workspace_diagnostics_async, timeout_ms=WORKSPACE_DIAGNOSTICS_TIMEOUT) self.do_semantic_tokens_async(view) if userprefs().link_highlight_style in ("underline", "none"): self._do_document_link_async(view, version) @@ -461,8 +468,9 @@ def do_document_diagnostic_async(self, view: sublime.View, version: Optional[int identifier = self.get_capability("diagnosticProvider.identifier") if identifier: params['identifier'] = identifier - if self.document_diagnostic_result_id: - params['previousResultId'] = self.document_diagnostic_result_id + result_id = self.session.diagnostics_result_ids.get(self.last_known_uri) + if result_id is not None: + params['previousResultId'] = result_id self.document_diagnostic_pending_response = self.session.send_request_async( Request.documentDiagnostic(params, view), partial(self._on_document_diagnostic_async, version), @@ -476,7 +484,7 @@ def _on_document_diagnostic_async(self, version: int, response: DocumentDiagnost def _apply_document_diagnostic_async( self, view: Optional[sublime.View], response: DocumentDiagnosticReport ) -> None: - self.document_diagnostic_result_id = response.get('resultId') + self.session.diagnostics_result_ids[self.last_known_uri] = response.get('resultId') if is_full_document_diagnostic_report(response): self.session.m_textDocument_publishDiagnostics( {'uri': self.last_known_uri, 'diagnostics': response['items']}) @@ -486,14 +494,15 @@ def _apply_document_diagnostic_async( cast(SessionBuffer, sb)._apply_document_diagnostic_async( None, cast(DocumentDiagnosticReport, diagnostic_report)) - def _on_document_diagnostic_error_async(self, version: int, error: Error) -> None: + def _on_document_diagnostic_error_async(self, version: int, error: ResponseError) -> None: self.document_diagnostic_pending_response = None - if error.code == LSPErrorCodes.ServerCancelled and is_diagnostic_server_cancellation_data(error.data) and \ - error.data['retriggerRequest']: - # Retrigger the request after a short delay, but only if there were no additional changes to the buffer (in - # that case the request will be retriggered automatically anyway) - sublime.set_timeout_async( - lambda: self._if_view_unchanged(self.do_document_diagnostic_async, version)(version), 500) + if error['code'] == LSPErrorCodes.ServerCancelled: + data = error.get('data') + if is_diagnostic_server_cancellation_data(data) and data['retriggerRequest']: + # Retrigger the request after a short delay, but only if there were no additional changes to the buffer + # (in that case the request will be retriggered automatically anyway) + sublime.set_timeout_async( + lambda: self._if_view_unchanged(self.do_document_diagnostic_async, version)(version), 500) def set_document_diagnostic_pending_refresh(self, needs_refresh: bool = True) -> None: self.document_diagnostic_needs_refresh = needs_refresh diff --git a/sublime-package.json b/sublime-package.json index 4d86b1cbd..d780669ac 100644 --- a/sublime-package.json +++ b/sublime-package.json @@ -298,10 +298,18 @@ } ] }, - "ClientHideNonProjectDiagnostics": { - "markdownDescription": "Whether diagnostics are ignored for files that are not within the project folders.", - "type": "boolean", - "default": false, + "ClientDiagnosticsMode": { + "markdownDescription": "Controls whether diagnostics are ignored for files that are not within the project folders. If the server supports \"pull diagnostics\", this also controls whether diagnostics are requested only for open files, or for all files contained in the project folders.", + "type": "string", + "enum": [ + "open_files", + "workspace" + ], + "markdownEnumDescriptions": [ + "Diagnostics are requested only for open files and they are cleared when the file closes.", + "Diagnostics are requested for all files in the project folders (only for servers with `diagnosticProvider.workspaceDiagnostics` capability). Diagnostics for files not within the project folders are ignored." + ], + "default": "open_files" }, "ClientPrioritySelector": { "markdownDescription": "While the `\"selector\"` is used to determine which views belong to which language server configuration, the `\"priority_selector\"` is used to determine which language server wins at the caret position in case there are multiple language servers attached to a view. For instance, there can only be one signature help popup visible at any given time. This selector is use to decide which one to use for such capabilities. This setting is optional and you won't need to set it if you're planning on using a single language server for a particular type of view." @@ -366,8 +374,8 @@ "feature_selector": { "$ref": "#/definitions/useSelectorInstead" }, - "hide_non_project_diagnostics": { - "$ref": "#/definitions/ClientHideNonProjectDiagnostics" + "diagnostics_mode": { + "$ref": "#/definitions/ClientDiagnosticsMode" }, "syntaxes": { "type": "array", @@ -753,8 +761,8 @@ "schemes": { "$ref": "sublime://settings/LSP#/definitions/ClientSchemes" }, - "hide_non_project_diagnostics": { - "$ref": "sublime://settings/LSP#/definitions/ClientHideNonProjectDiagnostics" + "diagnostics_mode": { + "$ref": "sublime://settings/LSP#/definitions/ClientDiagnosticsMode" }, "priority_selector": { "$ref": "sublime://settings/LSP#/definitions/ClientPrioritySelector"