Repository: sublimelsp/LSP

Issue:
Ignores diagnostics that shouldn't be ignored
**Describe the bug**

The logic to ignore diagnostics when the file is outside the project is erroneously ignoring diagnostics in some cases.

**To Reproduce**

With LSP-json installed, open some project and run `Project: Edit Project` from the the command palette and add some invalid property.

The project configuration that opens might live outside of the project folder (it's a personal choice but for me it always does since I'm using "Project Manager" package).

Expected that diagnostics are not ignored in that case.


Discussion:
Should we just add a client config option for that? What it should be named? `include_non_project_diagnostics` (defaults to `false`)? Or inverted - `ignore_non_project_diagnostics` (defaults to `true`)?

Naming is the hardest part.
As far as I understand, this config option would be set dependent on whether the server is "project-based" (like most servers are), or "single-file-based" (e.g. LSP-json, LSP-html, LSP-css). I think there is no real definition of that in the LSP specs and it can't be derived from for example the initialize response.

How about naming the client config key maybe "project_aware" or "project_based" then, defaulting to `true`? It could be useful for more things than just whether to ignore diagnostics or not, in the future.
I'm not sure if it's worth making the option so generic and make the meaning not so clear on the first look. Especially since we don't currently have any idea what else might go under that option in the future.

An alternative suggestion I have is `single_file_mode` that, I believe, I've seen used somewhere else (maybe neovim) and I think I like that one the most.
> An alternative suggestion I have is `single_file_mode` that, I believe, I've seen used somewhere else (maybe neovim) and I think I like that one the most.

Wouldn't that just be the negation of `project_aware`? :thinking:

I think `single_file_mode` could be misleading, because I would use that to describe the current mode of a session. In other words, a single server, e.g. pyright can be both in a project mode (when a folder was opened in the sidebar), and in a single-file mode (when you just open a single file in ST). There are already differences in LSP behavior dependent on those two situations.
Maybe better to have a setting that is specifically handling the issue at hand since it's not clear whether more generic naming would be useful for anything in the future. And it's hard to figure out a good name for the more generic option anyway, not knowing what it might be used in the future.
Going back and forth on the naming of the new option.

Been thinking about `project_aware` again in context of https://github.com/sublimelsp/LSP/issues/2120 but what makes me think that it's not a good option is the fact that someone might want to enable out-of-project diagnostics even when server is project-aware because server might still support those fine.
Yeah, I agree now that something like `ignore_non_project_diagnostics` is probably better.

Resolving patch:
diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py
--- a/plugin/core/sessions.py
+++ b/plugin/core/sessions.py
@@ -175,7 +175,7 @@ def get_project_path(self, file_path: str) -> Optional[str]:
         raise NotImplementedError()
 
     @abstractmethod
-    def should_present_diagnostics(self, uri: DocumentUri) -> Optional[str]:
+    def should_present_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> Optional[str]:
         """
         Should the diagnostics for this URI be shown in the view? Return a reason why not
         """
@@ -1751,7 +1751,7 @@ def m_textDocument_publishDiagnostics(self, params: PublishDiagnosticsParams) ->
         if not mgr:
             return
         uri = params["uri"]
-        reason = mgr.should_present_diagnostics(uri)
+        reason = mgr.should_present_diagnostics(uri, self.config)
         if isinstance(reason, str):
             return debug("ignoring unsuitable diagnostics for", uri, "reason:", reason)
         diagnostics = params["diagnostics"]
diff --git a/plugin/core/types.py b/plugin/core/types.py
--- a/plugin/core/types.py
+++ b/plugin/core/types.py
@@ -651,6 +651,7 @@ def __init__(self,
                  disabled_capabilities: DottedDict = DottedDict(),
                  file_watcher: FileWatcherConfig = {},
                  semantic_tokens: Optional[Dict[str, str]] = None,
+                 hide_non_project_diagnostics: bool = False,
                  path_maps: Optional[List[PathMap]] = None) -> None:
         self.name = name
         self.selector = selector
@@ -676,6 +677,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
 
     @classmethod
     def from_sublime_settings(cls, name: str, s: sublime.Settings, file: str) -> "ClientConfig":
@@ -708,6 +710,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)),
             path_maps=PathMap.parse(s.get("path_maps"))
         )
 
@@ -737,6 +740,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),
             path_maps=PathMap.parse(d.get("path_maps"))
         )
 
@@ -766,6 +770,8 @@ 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),
             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
--- a/plugin/core/windows.py
+++ b/plugin/core/windows.py
@@ -344,11 +344,11 @@ def get_project_path(self, file_path: str) -> Optional[str]:
                     candidate = folder
         return candidate
 
-    def should_present_diagnostics(self, uri: DocumentUri) -> Optional[str]:
+    def should_present_diagnostics(self, uri: DocumentUri, configuration: ClientConfig) -> Optional[str]:
         scheme, path = parse_uri(uri)
         if scheme != "file":
             return None
-        if not self._workspace.contains(path):
+        if configuration.hide_non_project_diagnostics and not self._workspace.contains(path):
             return "not inside window folders"
         view = self._window.active_view()
         if not view:


Test patch: