Repository: sublimelsp/LSP

Issue:
organizeImports and fixAll in a loop!
Hi. Got a weird issue that happens:

Code gets reorganised to this via the `"source.organizeImports": true` option (note the extra spacing before each import):

```
import {
    useAcceptContracts,
    useAddressUpdate,
    useReserveAppointment,
    useSetVisitAttributes,
    useSetVisitContactDetails,
    useSubmitCancellationReasons,
    useSubmitOrder,
    useUpdateVisitReasons
} from '../../utilities/hooks';
```

I fix all autofixable eslint issues (the issue is remove the extra spacing before each import):

```
import {
  useAcceptContracts,
  useAddressUpdate,
  useReserveAppointment,
  useSetVisitAttributes,
  useSetVisitContactDetails,
  useSubmitCancellationReasons,
  useSubmitOrder,
  useUpdateVisitReasons
} from '../../utilities/hooks';
```

Save it and it reverts to the 1st code where it has double spaces! Saving via the Sublime menu does not trigger the LSP commands so is a way out of the loop.

Not sure it could be anything other than LSP as saving via cmd + s works when this is false `"source.organizeImports": false`.

**Expected behavior**
The `source.organizeImports` should just add one space otherwise that command and the `source.fixAll` command seem to be stuck in a loop!

**Environment (please complete the following information):**
- OS:macOS 13.2.1
- Sublime Text version: 4143
- LSP version: v1.23.0
- Language servers used: eslint (for js and jsx files)



Discussion:
if we are talking about `source.organizeImports` then it's LSP-typescript, not LSP-eslint.

But yes, LSP-typescript does have that issue which should possibly be addressed in `LSP` (here) instead.

see https://github.com/sublimelsp/LSP-typescript/issues/202

So thats what that issue was! I was seeing too many undos that I did not do. OK thanks for the info. Hopefully it gets fixed :)
For more info, also see #2270

Resolving patch:
diff --git a/plugin/code_actions.py b/plugin/code_actions.py
--- a/plugin/code_actions.py
+++ b/plugin/code_actions.py
@@ -253,20 +253,12 @@ def _request_code_actions_async(self) -> None:
     def _handle_response_async(self, responses: List[CodeActionsByConfigName]) -> None:
         if self._cancelled:
             return
-        document_version = self._task_runner.view.change_count()
         tasks = []  # type: List[Promise]
         for config_name, code_actions in responses:
             session = self._task_runner.session_by_name(config_name, 'codeActionProvider')
             if session:
                 tasks.extend([session.run_code_action_async(action, progress=False) for action in code_actions])
-        Promise.all(tasks).then(lambda _: self._on_code_actions_completed(document_version))
-
-    def _on_code_actions_completed(self, previous_document_version: int) -> None:
-        if previous_document_version != self._task_runner.view.change_count():
-            # Give on_text_changed_async a chance to trigger.
-            sublime.set_timeout_async(self._request_code_actions_async)
-        else:
-            self._on_complete()
+        Promise.all(tasks).then(lambda _: self._on_complete())
 
 
 LspSaveCommand.register_task(CodeActionOnSaveTask)


Test patch:
diff --git a/tests/test_code_actions.py b/tests/test_code_actions.py
--- a/tests/test_code_actions.py
+++ b/tests/test_code_actions.py
@@ -142,7 +142,7 @@ def test_requests_with_diagnostics(self) -> Generator:
         self.assertEquals(entire_content(self.view), 'const x = 1;')
         self.assertEquals(self.view.is_dirty(), False)
 
-    def test_applies_in_two_iterations(self) -> Generator:
+    def test_applies_only_one_pass(self) -> Generator:
         self.insert_characters('const x = 1')
         initial_change_count = self.view.change_count()
         yield from self.await_client_notification(
@@ -179,7 +179,7 @@ def test_applies_in_two_iterations(self) -> Generator:
         self.view.run_command('lsp_save')
         # Wait for the view to be saved
         yield lambda: not self.view.is_dirty()
-        self.assertEquals(entire_content(self.view), 'const x = 1;\nAnd again!')
+        self.assertEquals(entire_content(self.view), 'const x = 1;')
 
     def test_applies_immediately_after_text_change(self) -> Generator:
         self.insert_characters('const x = 1')