diff --git a/plugin/core/input_handlers.py b/plugin/core/input_handlers.py index cfd414c4d..8a3b9fdef 100644 --- a/plugin/core/input_handlers.py +++ b/plugin/core/input_handlers.py @@ -85,7 +85,7 @@ def get_list_items(self) -> ListItemsReturn: class DynamicListInputHandler(sublime_plugin.ListInputHandler, metaclass=ABCMeta): """ A ListInputHandler which can update its items while typing in the input field. - Subclasses of PreselectedListInputHandler must not implement the `list_items` method, but can override + Subclasses of DynamicListInputHandler must not implement the `list_items` method, but can override `get_list_items` for the initial list items. The `on_modified` method will be called after a small delay (debounced) whenever changes were made to the input text. You can use this to call the `update` method with a list of `ListInputItem`s to update the list items. diff --git a/plugin/core/sessions.py b/plugin/core/sessions.py index d7df2cfb5..c7a09c3da 100644 --- a/plugin/core/sessions.py +++ b/plugin/core/sessions.py @@ -948,7 +948,10 @@ def should_ignore(cls, view: sublime.View) -> bool: """ Exclude a view from being handled by the language server, even if it matches the URI scheme(s) and selector from the configuration. This can be used to, for example, ignore certain file patterns which are listed in a - configuration file (e.g. .gitignore). + configuration file (e.g. .gitignore). Please note that this also means that no document syncronization + notifications (textDocument/didOpen, textDocument/didChange, textDocument/didClose, etc.) are sent to the server + for ignored views, when they are opened in the editor. Therefore this method should be used with caution for + language servers which index all files in the workspace. """ return False diff --git a/plugin/session_buffer.py b/plugin/session_buffer.py index deb5f85c5..e2c6907de 100644 --- a/plugin/session_buffer.py +++ b/plugin/session_buffer.py @@ -317,7 +317,7 @@ def purge_changes_async(self, view: sublime.View, suppress_requests: bool = Fals return if sync_kind == TextDocumentSyncKind.Full: changes = None - version = view.change_count() if view.is_valid() else self._pending_changes.version + version = view.change_count() or self._pending_changes.version else: changes = self._pending_changes.changes version = self._pending_changes.version @@ -336,19 +336,22 @@ def _on_after_change_async(self, view: sublime.View, version: int, suppress_requ if self._is_saving: self._has_changed_during_save = True return - if suppress_requests or not view.is_valid(): + if suppress_requests: return - self._do_color_boxes_async(view, version) - self.do_document_diagnostic_async(view, version) - if self.session.config.diagnostics_mode == "workspace" and \ - not self.session.workspace_diagnostics_pending_response and \ - self.session.has_capability('diagnosticProvider.workspaceDiagnostics'): - self._workspace_diagnostics_debouncer_async.debounce( - self.session.do_workspace_diagnostics_async, timeout_ms=WORKSPACE_DIAGNOSTICS_TIMEOUT) - self.do_semantic_tokens_async(view) - if userprefs().link_highlight_style in ("underline", "none"): - self._do_document_link_async(view, version) - self.do_inlay_hints_async(view) + try: + self._do_color_boxes_async(view, version) + self.do_document_diagnostic_async(view, version) + if self.session.config.diagnostics_mode == "workspace" and \ + not self.session.workspace_diagnostics_pending_response and \ + self.session.has_capability('diagnosticProvider.workspaceDiagnostics'): + self._workspace_diagnostics_debouncer_async.debounce( + self.session.do_workspace_diagnostics_async, timeout_ms=WORKSPACE_DIAGNOSTICS_TIMEOUT) + self.do_semantic_tokens_async(view) + if userprefs().link_highlight_style in ("underline", "none"): + self._do_document_link_async(view, version) + self.do_inlay_hints_async(view) + except MissingUriError: + pass def on_pre_save_async(self, view: sublime.View) -> None: self._is_saving = True diff --git a/tests/test_single_document.py b/tests/test_single_document.py index 4d1bb10d3..673f1842f 100644 --- a/tests/test_single_document.py +++ b/tests/test_single_document.py @@ -84,31 +84,6 @@ def test_did_close(self) -> 'Generator': self.view.close() yield from self.await_message("textDocument/didClose") - def test_did_change(self) -> 'Generator': - assert self.view - self.maxDiff = None - self.insert_characters("A") - yield from self.await_message("textDocument/didChange") - # multiple changes are batched into one didChange notification - self.insert_characters("B\n") - self.insert_characters("🙂\n") - self.insert_characters("D") - promise = YieldPromise() - yield from self.await_message("textDocument/didChange", promise) - self.assertEqual(promise.result(), { - 'contentChanges': [ - {'rangeLength': 0, 'range': {'start': {'line': 0, 'character': 1}, 'end': {'line': 0, 'character': 1}}, 'text': 'B'}, # noqa - {'rangeLength': 0, 'range': {'start': {'line': 0, 'character': 2}, 'end': {'line': 0, 'character': 2}}, 'text': '\n'}, # noqa - {'rangeLength': 0, 'range': {'start': {'line': 1, 'character': 0}, 'end': {'line': 1, 'character': 0}}, 'text': '🙂'}, # noqa - # Note that this is character offset (2) is correct (UTF-16). - {'rangeLength': 0, 'range': {'start': {'line': 1, 'character': 2}, 'end': {'line': 1, 'character': 2}}, 'text': '\n'}, # noqa - {'rangeLength': 0, 'range': {'start': {'line': 2, 'character': 0}, 'end': {'line': 2, 'character': 0}}, 'text': 'D'}], # noqa - 'textDocument': { - 'version': self.view.change_count(), - 'uri': filename_to_uri(TEST_FILE_PATH) - } - }) - def test_sends_save_with_purge(self) -> 'Generator': assert self.view self.view.settings().set("lsp_format_on_save", False) @@ -371,6 +346,54 @@ def test_progress(self) -> 'Generator': self.assertEqual(result, {"general": "kenobi"}) +class SingleDocumentTestCase2(TextDocumentTestCase): + + def test_did_change(self) -> 'Generator': + assert self.view + self.maxDiff = None + self.insert_characters("A") + yield from self.await_message("textDocument/didChange") + # multiple changes are batched into one didChange notification + self.insert_characters("B\n") + self.insert_characters("🙂\n") + self.insert_characters("D") + promise = YieldPromise() + yield from self.await_message("textDocument/didChange", promise) + self.assertEqual(promise.result(), { + 'contentChanges': [ + {'rangeLength': 0, 'range': {'start': {'line': 0, 'character': 1}, 'end': {'line': 0, 'character': 1}}, 'text': 'B'}, # noqa + {'rangeLength': 0, 'range': {'start': {'line': 0, 'character': 2}, 'end': {'line': 0, 'character': 2}}, 'text': '\n'}, # noqa + {'rangeLength': 0, 'range': {'start': {'line': 1, 'character': 0}, 'end': {'line': 1, 'character': 0}}, 'text': '🙂'}, # noqa + # Note that this is character offset (2) is correct (UTF-16). + {'rangeLength': 0, 'range': {'start': {'line': 1, 'character': 2}, 'end': {'line': 1, 'character': 2}}, 'text': '\n'}, # noqa + {'rangeLength': 0, 'range': {'start': {'line': 2, 'character': 0}, 'end': {'line': 2, 'character': 0}}, 'text': 'D'}], # noqa + 'textDocument': { + 'version': self.view.change_count(), + 'uri': filename_to_uri(TEST_FILE_PATH) + } + }) + + +class SingleDocumentTestCase3(TextDocumentTestCase): + + @classmethod + def get_test_name(cls) -> str: + return "testfile2" + + def test_did_change_before_did_close(self) -> 'Generator': + assert self.view + self.view.window().run_command("chain", { + "commands": [ + ["insert", {"characters": "TEST"}], + ["save", {"async": False}], + ["close", {}] + ] + }) + yield from self.await_message('textDocument/didChange') + # yield from self.await_message('textDocument/didSave') # TODO why is this not sent? + yield from self.await_message('textDocument/didClose') + + class WillSaveWaitUntilTestCase(TextDocumentTestCase): @classmethod diff --git a/tests/testfile2.txt b/tests/testfile2.txt new file mode 100644 index 000000000..e69de29bb