Repository: sublimelsp/LSP

Issue:
Format JSON on save
Is there a way to get this LSP to format json files ons save?

It works for other LSPs but just not JSON,

Here are my configs:

Preferences
```
{
        ...
	"lsp_format_on_save": true
        ...
}
```

LSP
```
	"lsp_code_actions_on_save": {
		"source.organizeImports": true,
		"source.fixAll.eslint": true,
	}
```

All LSP-JSON settings are default


Discussion:
That server doesn't provide "documentFormattingProvider" but it does provide "documentRangeFormattingProvider" which we can use to format the whole document. We already do that for the "LSP: Format file" command and VSCode also does that for the "format on save" feature.

So we can fix that.

Resolving patch:
diff --git a/plugin/code_actions.py b/plugin/code_actions.py
--- a/plugin/code_actions.py
+++ b/plugin/code_actions.py
@@ -244,22 +244,22 @@ def run_async(self) -> None:
 
     def _request_code_actions_async(self) -> None:
         self._purge_changes_async()
-        on_save_actions = self._get_code_actions_on_save(self._view)
-        actions_manager.request_on_save(self._view, self._handle_response_async, on_save_actions)
+        on_save_actions = self._get_code_actions_on_save(self._task_runner.view)
+        actions_manager.request_on_save(self._task_runner.view, self._handle_response_async, on_save_actions)
 
     def _handle_response_async(self, responses: CodeActionsByConfigName) -> None:
         if self._cancelled:
             return
-        document_version = self._view.change_count()
+        document_version = self._task_runner.view.change_count()
         tasks = []  # type: List[Promise]
         for config_name, code_actions in responses.items():
             if code_actions:
-                for session in sessions_for_view(self._view, 'codeActionProvider'):
+                for session in self._task_runner.sessions('codeActionProvider'):
                     if session.config.name == config_name:
                         for code_action in code_actions:
                             tasks.append(session.run_code_action_async(code_action, progress=False))
                         break
-        if document_version != self._view.change_count():
+        if document_version != self._task_runner.view.change_count():
             # Give on_text_changed_async a chance to trigger.
             Promise.all(tasks).then(lambda _: sublime.set_timeout_async(self._request_code_actions_async))
         else:
diff --git a/plugin/formatting.py b/plugin/formatting.py
--- a/plugin/formatting.py
+++ b/plugin/formatting.py
@@ -1,7 +1,6 @@
 from .core.edit import parse_text_edit
 from .core.protocol import TextEdit
 from .core.registry import LspTextCommand
-from .core.registry import sessions_for_view
 from .core.sessions import Session
 from .core.settings import userprefs
 from .core.typing import Any, Callable, List, Optional, Iterator
@@ -24,13 +23,13 @@ class WillSaveWaitTask(SaveTask):
     def is_applicable(cls, view: sublime.View) -> bool:
         return bool(view.file_name())
 
-    def __init__(self, view: sublime.View, on_complete: Callable[[], None]) -> None:
-        super().__init__(view, on_complete)
+    def __init__(self, task_runner: LspTextCommand, on_complete: Callable[[], None]) -> None:
+        super().__init__(task_runner, on_complete)
         self._session_iterator = None  # type: Optional[Iterator[Session]]
 
     def run_async(self) -> None:
         super().run_async()
-        self._session_iterator = sessions_for_view(self._view, 'textDocumentSync.willSaveWaitUntil')
+        self._session_iterator = self._task_runner.sessions('textDocumentSync.willSaveWaitUntil')
         self._handle_next_session_async()
 
     def _handle_next_session_async(self) -> None:
@@ -43,13 +42,13 @@ def _handle_next_session_async(self) -> None:
 
     def _will_save_wait_until_async(self, session: Session) -> None:
         session.send_request_async(
-            will_save_wait_until(self._view, reason=1),  # TextDocumentSaveReason.Manual
+            will_save_wait_until(self._task_runner.view, reason=1),  # TextDocumentSaveReason.Manual
             self._on_response,
             lambda error: self._on_response(None))
 
     def _on_response(self, response: Any) -> None:
         if response and not self._cancelled:
-            apply_response_to_view(response, self._view)
+            apply_response_to_view(response, self._task_runner.view)
         sublime.set_timeout_async(self._handle_next_session_async)
 
 
@@ -64,19 +63,17 @@ def is_applicable(cls, view: sublime.View) -> bool:
     def run_async(self) -> None:
         super().run_async()
         self._purge_changes_async()
-        self._format_on_save_async()
-
-    def _format_on_save_async(self) -> None:
-        session = next(sessions_for_view(self._view, 'documentFormattingProvider'), None)
+        session = self._task_runner.best_session(LspFormatDocumentCommand.capability)
         if session:
             session.send_request_async(
-                text_document_formatting(self._view), self._on_response, lambda error: self._on_response(None))
+                text_document_formatting(self._task_runner.view), self._on_response,
+                lambda error: self._on_response(None))
         else:
             self._on_complete()
 
     def _on_response(self, response: Any) -> None:
         if response and not self._cancelled:
-            apply_response_to_view(response, self._view)
+            apply_response_to_view(response, self._task_runner.view)
         sublime.set_timeout_async(self._on_complete)
 
 
diff --git a/plugin/save_command.py b/plugin/save_command.py
--- a/plugin/save_command.py
+++ b/plugin/save_command.py
@@ -1,3 +1,4 @@
+from .core.registry import LspTextCommand
 from .core.typing import Callable, List, Type
 from abc import ABCMeta, abstractmethod
 import sublime
@@ -8,8 +9,6 @@ class SaveTask(metaclass=ABCMeta):
     """
     Base class for tasks that run on save.
 
-    Takes care of timing out lask after specified timeout provided that base run() is called.
-
     Note: The whole task runs on the async thread.
     """
 
@@ -20,8 +19,8 @@ class SaveTask(metaclass=ABCMeta):
     def is_applicable(cls, view: sublime.View) -> bool:
         pass
 
-    def __init__(self, view: sublime.View, on_done: Callable[[], None]):
-        self._view = view
+    def __init__(self, task_runner: LspTextCommand, on_done: Callable[[], None]):
+        self._task_runner = task_runner
         self._on_done = on_done
         self._completed = False
         self._cancelled = False
@@ -44,11 +43,11 @@ def cancel(self) -> None:
         self._cancelled = True
 
     def _set_view_status(self, text: str) -> None:
-        self._view.set_status(self._status_key, text)
+        self._task_runner.view.set_status(self._status_key, text)
         sublime.set_timeout_async(self._erase_view_status, 5000)
 
     def _erase_view_status(self) -> None:
-        self._view.erase_status(self._status_key)
+        self._task_runner.view.erase_status(self._status_key)
 
     def _on_complete(self) -> None:
         assert not self._completed
@@ -58,14 +57,14 @@ def _on_complete(self) -> None:
 
     def _purge_changes_async(self) -> None:
         # Supermassive hack that will go away later.
-        listeners = sublime_plugin.view_event_listeners.get(self._view.id(), [])
+        listeners = sublime_plugin.view_event_listeners.get(self._task_runner.view.id(), [])
         for listener in listeners:
             if listener.__class__.__name__ == 'DocumentSyncListener':
                 listener.purge_changes_async()  # type: ignore
                 break
 
 
-class LspSaveCommand(sublime_plugin.TextCommand):
+class LspSaveCommand(LspTextCommand):
     """
     A command used as a substitute for native save command. Runs code actions and document
     formatting before triggering the native save command.
@@ -89,7 +88,7 @@ def run(self, edit: sublime.Edit) -> None:
         sublime.set_timeout_async(self._trigger_on_pre_save_async)
         for Task in self._tasks:
             if Task.is_applicable(self.view):
-                self._pending_tasks.append(Task(self.view, self._on_task_completed_async))
+                self._pending_tasks.append(Task(self, self._on_task_completed_async))
         if self._pending_tasks:
             sublime.set_timeout_async(self._run_next_task_async)
         else:


Test patch: