Repository: sublimelsp/LSP

Issue:
Should not trigger non-essential requests during save
**Describe the bug**

I've noticed that during saving a semantic highlighting and inlay hints are requested. This most likely slows down the saving "task" which is waiting for the code actions response from the server.

**To Reproduce**

With LSP-typescript for example, and semantic highlighting enabled, change the document and save it immediately. The log shows:

```
::  -> LSP-eslint textDocument/didChange: {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts', 'version': 33}, 'contentChanges': [{'text': '\n', 'rangeLength': 0, 'range': {'end': {'line': 62, 'character': 109}, 'start': {'line': 62, 'character': 109}}}, {'text': '        ', 'rangeLength': 0, 'range': {'end': {'line': 63, 'character': 0}, 'start': {'line': 63, 'character': 0}}}, {'text': '', 'rangeLength': 8, 'range': {'end': {'line': 63, 'character': 8}, 'start': {'line': 63, 'character': 0}}}, {'text': '            ', 'rangeLength': 0, 'range': {'end': {'line': 63, 'character': 0}, 'start': {'line': 63, 'character': 0}}}]}
::  -> LSP-typescript textDocument/didChange: {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts', 'version': 33}, 'contentChanges': [{'text': '\n', 'rangeLength': 0, 'range': {'end': {'line': 62, 'character': 109}, 'start': {'line': 62, 'character': 109}}}, {'text': '        ', 'rangeLength': 0, 'range': {'end': {'line': 63, 'character': 0}, 'start': {'line': 63, 'character': 0}}}, {'text': '', 'rangeLength': 8, 'range': {'end': {'line': 63, 'character': 8}, 'start': {'line': 63, 'character': 0}}}, {'text': '            ', 'rangeLength': 0, 'range': {'end': {'line': 63, 'character': 0}, 'start': {'line': 63, 'character': 0}}}]}
:: --> LSP-eslint textDocument/codeAction(763): {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts'}, 'context': {'triggerKind': 2, 'only': ['source.fixAll.eslint'], 'diagnostics': []}, 'range': {'end': {'line': 102, 'character': 0}, 'start': {'line': 0, 'character': 0}}}
:: --> LSP-typescript textDocument/codeAction(244): {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts'}, 'context': {'triggerKind': 2, 'only': ['source.fixAll.ts', 'source.addMissingImports.ts'], 'diagnostics': []}, 'range': {'end': {'line': 102, 'character': 0}, 'start': {'line': 0, 'character': 0}}}
:: --> LSP-typescript textDocument/semanticTokens/full(245): {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts'}}
:: <<< LSP-typescript 244: []
:: <-  LSP-eslint textDocument/publishDiagnostics: {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts', 'diagnostics': [{'message': "Parsing error: ',' expected.", 'source': 'eslint', 'severity': 1, 'range': {'end': {'line': 63, 'character': 11}, 'start': {'line': 63, 'character': 11}}}]}
:: --> LSP-eslint textDocument/codeAction(764): {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts'}, 'context': {'triggerKind': 2, 'diagnostics': []}, 'range': {'end': {'line': 63, 'character': 12}, 'start': {'line': 63, 'character': 12}}}
:: --> LSP-typescript textDocument/codeAction(246): {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts'}, 'context': {'triggerKind': 2, 'diagnostics': []}, 'range': {'end': {'line': 63, 'character': 12}, 'start': {'line': 63, 'character': 12}}}
:: <-  LSP-eslint eslint/status: {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts', 'state': 1}
:: <<< LSP-eslint 763: []
:: <<< LSP-typescript 245: {'data': [22, 13, 28, 0, 1, 1, 27, 10, 9, 11, 0, 13, 3, 0, 0, 0, 4, 4, 9, 10, 2, 24, 17, 11, 7, 1, 8, 3, 6, 1, 0, 9, 11, 5, 0, 1, 8, 5, 6, 1, 0, 11, 5, 2, 0, 1, 8, 9, 6, 1, 0, 11, 12, 0, 0, 1, 8, 9, 6, 1, 0, 11, 9, 0, 0, 1, 8, 9, 6, 1, 0, 11, 9, 2, 0, 1, 8, 20, 6, 1, 0, 22, 20, 0, 0, 1, 8, 5, 6, 1, 0, 12, 17, 2, 0, 1, 7, 7, 2, 16, 0, 12, 9, 5, 0, 1, 12, 9, 6, 0, 0, 10, 10, 9, 0, 0, 11, 2, 11, 0, 0, 3, 28, 0, 0, 0, 29, 10, 9, 10, 1, 12, 9, 6, 0, 0, 10, 16, 11, 0, 4, 14, 4, 7, 41, 0, 7, 9, 10, 0, 0, 10, 3, 6, 0, 2, 13, 4, 7, 40, 1, 12, 9, 6, 0, 0, 10, 16, 11, 0, 4, 14, 8, 7, 41, 0, 11, 9, 6, 0, 0, 10, 3, 11, 0, 0, 4, 4, 7, 40, 2, 13, 8, 7, 40, 1, 12, 9, 6, 0, 0, 10, 16, 11, 0, 4, 13, 27, 10, 0, 0, 28, 20, 6, 0, 0, 22, 4, 7, 40, 4, 14, 20, 6, 0, 0, 21, 29, 11, 4, 0, 30, 4, 7, 40, 2, 14, 5, 7, 41, 0, 8, 8, 7, 40, 0, 9, 8, 11, 0, 0, 9, 5, 6, 0, 0, 6, 5, 9, 0, 1, 14, 6, 7, 41, 0, 9, 8, 7, 40, 0, 9, 8, 11, 0, 0, 9, 5, 6, 0, 0, 6, 3, 9, 0, 0, 7, 5, 7, 40, 2, 14, 8, 7, 41, 0, 17, 9, 6, 0, 0, 10, 7, 11, 0, 0, 8, 12, 1, 0, 0, 13, 17, 8, 8, 0, 21, 4, 9, 1, 0, 6, 5, 9, 1, 0, 7, 6, 9, 1, 2, 12, 8, 7, 40, 0, 9, 4, 9, 0, 0, 24, 8, 7, 40, 0, 9, 7, 9, 0, 0, 12, 8, 7, 40, 0, 9, 4, 9, 0, 4, 15, 8, 7, 40, 0, 9, 4, 9, 0, 0, 5, 3, 11, 16, 0, 8, 9, 5, 0, 0, 11, 4, 6, 1, 1, 18, 9, 7, 41, 0, 16, 9, 5, 0, 0, 10, 6, 10, 0, 1, 16, 8, 3, 0, 0, 9, 12, 10, 8, 0, 13, 4, 6, 0, 0, 5, 8, 9, 0, 1, 16, 4, 6, 0, 0, 5, 4, 9, 0, 1, 16, 25, 10, 0, 0, 26, 4, 6, 0, 0, 5, 4, 9, 0, 1, 12, 4, 6, 0, 0, 5, 16, 9, 0, 0, 21, 9, 7, 40, 0, 10, 11, 9, 0, 1, 12, 4, 6, 0, 0, 5, 15, 9, 0, 0, 20, 9, 7, 40, 0, 10, 12, 9, 0, 1, 19, 9, 7, 40, 5, 9, 27, 10, 1, 0, 28, 20, 6, 1, 0, 22, 20, 0, 0, 0, 22, 8, 6, 1, 1, 10, 11, 7, 41, 0, 14, 20, 6, 0, 0, 21, 14, 11, 0, 0, 15, 8, 6, 0, 4, 11, 11, 7, 40, 0, 12, 30, 9, 8, 1, 8, 11, 7, 40, 0, 12, 30, 9, 8, 1, 8, 11, 7, 40, 0, 12, 32, 9, 8, 1, 8, 11, 7, 40, 0, 12, 39, 9, 8, 1, 8, 11, 7, 40, 0, 12, 38, 9, 8, 1, 8, 11, 7, 40, 0, 12, 40, 9, 8, 1, 8, 11, 7, 40, 0, 12, 29, 9, 8, 3, 9, 25, 10, 1, 0, 26, 4, 6, 1, 0, 6, 2, 3, 0, 0, 3, 6, 3, 0, 0, 7, 8, 3, 0, 0, 9, 13, 5, 0, 0, 20, 13, 5, 0, 1, 12, 4, 6, 0, 1, 37, 13, 5, 0, 0, 14, 9, 7, 8, 1, 32, 13, 5, 0, 0, 14, 4, 7, 8]}
:: <<< LSP-eslint 764: []
:: <<< LSP-typescript 246: []
::  -> LSP-eslint textDocument/didSave: {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts'}}
::  -> LSP-typescript textDocument/didSave: {'textDocument': {'uri': 'file:///usr/local/workspace/github/typescript-language-server/src/features/inlay-hints.ts'}}
```

which indicates that we've triggered `semanticTokens` request during save task which can significantly slow down the save task that is waiting for code actions from the server.

**Expected behavior**

We should try to postpone non-essential requests so that those don't trigger during save.



Discussion:

Resolving patch:
diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py
--- a/plugin/session_buffer.py
+++ b/plugin/session_buffer.py
@@ -124,6 +124,8 @@ def __init__(self, session_view: SessionViewProtocol, buffer_id: int, uri: Docum
         self._last_semantic_region_key = 0
         self._inlay_hints_phantom_set = sublime.PhantomSet(view, "lsp_inlay_hints")
         self.inlay_hints_needs_refresh = False
+        self._is_saving = False
+        self._has_changed_during_save = False
         self._check_did_open(view)
         self._session.register_session_buffer_async(self)
 
@@ -309,6 +311,9 @@ def purge_changes_async(self, view: sublime.View) -> None:
         sublime.set_timeout_async(lambda: self._on_after_change_async(view, version))
 
     def _on_after_change_async(self, view: sublime.View, version: int) -> None:
+        if self._is_saving:
+            self._has_changed_during_save = True
+            return
         self._do_color_boxes_async(view, version)
         self.do_semantic_tokens_async(view)
         if userprefs().link_highlight_style in ("underline", "none"):
@@ -316,12 +321,14 @@ def _on_after_change_async(self, view: sublime.View, version: int) -> None:
         self.do_inlay_hints_async(view)
 
     def on_pre_save_async(self, view: sublime.View) -> None:
+        self._is_saving = True
         if self.should_notify_will_save():
             self.purge_changes_async(view)
             # TextDocumentSaveReason.Manual
             self.session.send_notification(will_save(self.last_known_uri, TextDocumentSaveReason.Manual))
 
     def on_post_save_async(self, view: sublime.View, new_uri: DocumentUri) -> None:
+        self._is_saving = False
         if new_uri != self.last_known_uri:
             self._check_did_close()
             self.last_known_uri = new_uri
@@ -331,6 +338,9 @@ def on_post_save_async(self, view: sublime.View, new_uri: DocumentUri) -> None:
             if send_did_save:
                 self.purge_changes_async(view)
                 self.session.send_notification(did_save(view, include_text, self.last_known_uri))
+        if self._has_changed_during_save:
+            self._has_changed_during_save = False
+            self._on_after_change_async(view, view.change_count())
 
     def some_view(self) -> Optional[sublime.View]:
         if not self.session_views:


Test patch: