Repository: sublimelsp/LSP

Issue:
When there's only a documentRangeFormattingProvider, do "full" formatting via that capability
It can be the case that the server advertises only as documentRangeFormattingProvider, but not as documentFormattingProvider.

In that case, still allow the user to format the entire file simply by passing the entire range of the view to the range formatter.

see also https://github.com/microsoft/vscode/issues/99513


Discussion:

Resolving patch:
diff --git a/plugin/core/views.py b/plugin/core/views.py
--- a/plugin/core/views.py
+++ b/plugin/core/views.py
@@ -151,12 +151,16 @@ def text_document_identifier(view: sublime.View) -> Dict[str, Any]:
     return {"uri": uri_from_view(view)}
 
 
+def entire_region(view: sublime.View) -> sublime.Region:
+    return sublime.Region(0, view.size())
+
+
 def entire_content(view: sublime.View) -> str:
-    return view.substr(sublime.Region(0, view.size()))
+    return view.substr(entire_region(view))
 
 
 def entire_content_range(view: sublime.View) -> Range:
-    return region_to_range(view, sublime.Region(0, view.size()))
+    return region_to_range(view, entire_region(view))
 
 
 def text_document_item(view: sublime.View, language_id: str) -> Dict[str, Any]:
diff --git a/plugin/formatting.py b/plugin/formatting.py
--- a/plugin/formatting.py
+++ b/plugin/formatting.py
@@ -5,7 +5,10 @@
 from .core.sessions import Session
 from .core.settings import settings
 from .core.typing import Any, List, Optional
-from .core.views import will_save_wait_until, text_document_formatting, text_document_range_formatting
+from .core.views import entire_region
+from .core.views import text_document_formatting
+from .core.views import text_document_range_formatting
+from .core.views import will_save_wait_until
 
 
 def apply_response_to_view(response: Optional[List[dict]], view: sublime.View) -> None:
@@ -59,13 +62,23 @@ class LspFormatDocumentCommand(LspTextCommand):
 
     capability = 'documentFormattingProvider'
 
+    def is_enabled(self, event: Optional[dict] = None) -> bool:
+        return super().is_enabled() or bool(self.session(LspFormatDocumentRangeCommand.capability))
+
     def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None:
         session = self.session(self.capability)
-        file_path = self.view.file_name()
-        if session and file_path:
-            session.send_request(
-                text_document_formatting(self.view),
-                lambda response: apply_response_to_view(response, self.view))
+        if session:
+            # Either use the documentFormattingProvider ...
+            session.send_request(text_document_formatting(self.view), self.on_result)
+        else:
+            session = self.session(LspFormatDocumentRangeCommand.capability)
+            if session:
+                # ... or use the documentRangeFormattingProvider and format the entire range.
+                req = text_document_range_formatting(self.view, entire_region(self.view))
+                session.send_request(req, self.on_result)
+
+    def on_result(self, params: Any) -> None:
+        apply_response_to_view(params, self.view)
 
 
 class LspFormatDocumentRangeCommand(LspTextCommand):
@@ -82,9 +95,7 @@ def is_enabled(self, event: Optional[dict] = None) -> bool:
 
     def run(self, edit: sublime.Edit, event: Optional[dict] = None) -> None:
         session = self.session(self.capability)
-        file_path = self.view.file_name()
-        if session and file_path:
-            region = self.view.sel()[0]
+        if session:
             session.send_request(
-                text_document_range_formatting(self.view, region),
+                text_document_range_formatting(self.view, self.view.sel()[0]),
                 lambda response: apply_response_to_view(response, self.view))


Test patch: