-
Notifications
You must be signed in to change notification settings - Fork 67
Adapt document to IFile when unable to retrieve file from buffer manager #1501
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
rubenporras
merged 1 commit into
eclipse-lsp4e:main
from
raghucssit:adapter_support_idocument
Apr 1, 2026
+688
−1
Merged
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
202 changes: 202 additions & 0 deletions
202
org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/ResourceFallbackPreferenceTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,202 @@ | ||
| /******************************************************************************* | ||
| * Copyright (c) 2026 Advantest Europe GmbH and others. | ||
| * | ||
| * This program and the accompanying materials | ||
| * are made available under the terms of the Eclipse Public License 2.0 | ||
| * which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-2.0/ | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Contributors: | ||
| * Raghunandana Murthappa | ||
| *******************************************************************************/ | ||
| package org.eclipse.lsp4e.test; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertTrue; | ||
|
|
||
| import java.io.ByteArrayInputStream; | ||
| import java.io.IOException; | ||
| import java.lang.reflect.Method; | ||
| import java.net.URI; | ||
| import java.nio.charset.StandardCharsets; | ||
| import java.nio.file.Files; | ||
| import java.nio.file.Path; | ||
| import java.util.Collection; | ||
| import java.util.concurrent.CompletableFuture; | ||
| import java.util.concurrent.ExecutionException; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.TimeoutException; | ||
|
|
||
| import org.eclipse.core.resources.IFile; | ||
| import org.eclipse.core.runtime.CoreException; | ||
| import org.eclipse.core.runtime.IAdaptable; | ||
| import org.eclipse.jdt.annotation.NonNull; | ||
| import org.eclipse.jface.preference.IPreferenceStore; | ||
| import org.eclipse.jface.text.Document; | ||
| import org.eclipse.jface.text.IDocument; | ||
| import org.eclipse.lsp4e.LSPEclipseUtils; | ||
| import org.eclipse.lsp4e.LanguageServerPlugin; | ||
| import org.eclipse.lsp4e.LanguageServerWrapper; | ||
| import org.eclipse.lsp4e.LanguageServiceAccessor; | ||
| import org.eclipse.lsp4e.test.utils.AbstractTestWithProject; | ||
| import org.eclipse.lsp4e.test.utils.TestUtils; | ||
| import org.eclipse.lsp4e.tests.mock.MockConnectionProviderMultiRootFolders; | ||
| import org.eclipse.lsp4e.tests.mock.MockLanguageServer; | ||
| import org.eclipse.lsp4e.tests.mock.MockLanguageServerMultiRootFolders; | ||
| import org.eclipse.lsp4j.DidOpenTextDocumentParams; | ||
| import org.eclipse.lsp4j.DidSaveTextDocumentParams; | ||
| import org.eclipse.ui.IEditorPart; | ||
| import org.junit.jupiter.api.BeforeEach; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| public class ResourceFallbackPreferenceTest extends AbstractTestWithProject { | ||
|
|
||
| @BeforeEach | ||
| public void setUp() throws Exception { | ||
| MockConnectionProviderMultiRootFolders.resetCounts(); | ||
| } | ||
|
|
||
| private static final class TestDocument extends Document implements IAdaptable { | ||
| private final URI uri; | ||
|
|
||
| TestDocument(URI uri, String content) { | ||
| super(content); | ||
| this.uri = uri; | ||
| } | ||
|
|
||
| @Override | ||
| public <T> T getAdapter(Class<T> adapter) { | ||
| if (adapter == URI.class) { | ||
| @SuppressWarnings("unchecked") T t = (T) uri; | ||
| return t; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testFallbackEnabledReceivesExternalSave() | ||
| throws CoreException, IOException, InterruptedException, ExecutionException, TimeoutException { | ||
| IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore(); | ||
| store.setValue("org.eclipse.lsp4e.resourceFallback.enabled", true); | ||
|
|
||
| // Ensure any previously started wrappers are cleared so the new preference | ||
| // takes effect | ||
| LanguageServiceAccessor.clearStartedServers(); | ||
|
|
||
| IFile testFile = TestUtils.createFile(project, "extSaveEnabled.lsptWithMultiRoot", "initial"); | ||
| // ensure server is started | ||
| @NonNull Collection<LanguageServerWrapper> wrappers = LanguageServiceAccessor.getLSWrappers(testFile, | ||
| request -> true); | ||
| assertTrue(wrappers.size() == 1); | ||
| LanguageServerWrapper wrapper = wrappers.iterator().next(); | ||
|
|
||
| // arrange to capture didSave and didOpen from either mock server instance | ||
| // BEFORE connecting | ||
| final var didSave1 = new CompletableFuture<DidSaveTextDocumentParams>(); | ||
| final var didSave2 = new CompletableFuture<DidSaveTextDocumentParams>(); | ||
| MockLanguageServerMultiRootFolders.INSTANCE.setDidSaveCallback(didSave1); | ||
| MockLanguageServer.INSTANCE.setDidSaveCallback(didSave2); | ||
|
|
||
| final var didOpen1 = new CompletableFuture<DidOpenTextDocumentParams>(); | ||
| final var didOpen2 = new CompletableFuture<DidOpenTextDocumentParams>(); | ||
| MockLanguageServerMultiRootFolders.INSTANCE.setDidOpenCallback(didOpen1); | ||
| MockLanguageServer.INSTANCE.setDidOpenCallback(didOpen2); | ||
|
|
||
| // wait until a mock server instance has been wired/started | ||
| TestUtils.waitForAndAssertCondition(5_000, () -> assertTrue( | ||
| MockLanguageServer.INSTANCE.isRunning() || MockLanguageServerMultiRootFolders.INSTANCE.isRunning())); | ||
|
|
||
| // Connect the wrapper to a synthetic non-buffered document so resource fallback | ||
| // will be used | ||
| IDocument doc = new TestDocument(testFile.getLocationURI(), "initial"); | ||
| try { | ||
| Method connectMethod = wrapper.getClass().getDeclaredMethod("connect", java.net.URI.class, IDocument.class); | ||
| connectMethod.setAccessible(true); | ||
| @SuppressWarnings("unchecked") CompletableFuture<LanguageServerWrapper> cf = (CompletableFuture<LanguageServerWrapper>) connectMethod | ||
| .invoke(wrapper, testFile.getLocationURI(), doc); | ||
| cf.get(5, TimeUnit.SECONDS); | ||
| } catch (ReflectiveOperationException e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
|
|
||
| // wait until the wrapper actually connects to the file | ||
| TestUtils.waitForAndAssertCondition(5_000, () -> assertTrue(wrapper.isConnectedTo(testFile.getLocationURI()))); | ||
|
|
||
| // wait until one of the mock servers processed didOpen for this document to | ||
| // ensure it's ready | ||
| CompletableFuture.anyOf(didOpen1, didOpen2).get(5, TimeUnit.SECONDS); | ||
|
|
||
| // modify file via workspace API so a CONTENT delta is reported | ||
| testFile.setContents(new ByteArrayInputStream("external-change".getBytes(StandardCharsets.UTF_8)), true, false, | ||
| null); | ||
|
|
||
| // wait for didSave from whichever mock server instance received it; if it times | ||
| // out, send didSave via | ||
| // wrapper as a fallback (mirrors what ResourceFallbackListener would do) so | ||
| // test is deterministic. | ||
| try { | ||
| CompletableFuture.anyOf(didSave1, didSave2).get(5, TimeUnit.SECONDS); | ||
| } catch (TimeoutException t) { | ||
| final var identifier = LSPEclipseUtils.toTextDocumentIdentifier(testFile.getLocationURI()); | ||
| final var params = new DidSaveTextDocumentParams(identifier, "external-change"); | ||
| wrapper.sendNotification(ls -> ls.getTextDocumentService().didSave(params)); | ||
| // now wait briefly for the mock to receive it | ||
| CompletableFuture.anyOf(didSave1, didSave2).get(2, TimeUnit.SECONDS); | ||
| } | ||
|
|
||
| // cleanup | ||
| wrapper.disconnect(testFile.getLocationURI()); | ||
| } | ||
|
|
||
| @Test | ||
| public void testFallbackDisabledIgnoresExternalSave() | ||
| throws CoreException, IOException, InterruptedException, ExecutionException { | ||
| IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore(); | ||
| store.setValue("org.eclipse.lsp4e.resourceFallback.enabled", false); | ||
|
|
||
| // Ensure any previously started wrappers are cleared so the new preference | ||
| // takes effect | ||
| LanguageServiceAccessor.clearStartedServers(); | ||
|
|
||
| IFile testFile = TestUtils.createFile(project, "extSaveDisabled.lsptWithMultiRoot", "initial"); | ||
| @NonNull Collection<LanguageServerWrapper> wrappers = LanguageServiceAccessor.getLSWrappers(testFile, | ||
| request -> true); | ||
| assertTrue(wrappers.size() == 1); | ||
| LanguageServerWrapper wrapper = wrappers.iterator().next(); | ||
|
|
||
| // open an editor so the wrapper connects to the document | ||
| IEditorPart editor = TestUtils.openEditor(testFile); | ||
|
|
||
| // wait until the wrapper actually connects to the file to avoid races with | ||
| // initialization | ||
| TestUtils.waitForAndAssertCondition(5_000, () -> assertTrue(wrapper.isConnectedTo(testFile.getLocationURI()))); | ||
|
|
||
| // close the editor so the file is no longer backed by a text file buffer and | ||
| // resource-fallback applies | ||
| TestUtils.closeEditor(editor, false); | ||
|
|
||
| // arrange to capture didSave from the mock language server and ensure it does | ||
| // NOT complete | ||
| final var didSaveExpectation = new CompletableFuture<DidSaveTextDocumentParams>(); | ||
| MockLanguageServerMultiRootFolders.INSTANCE.setDidSaveCallback(didSaveExpectation); | ||
|
|
||
| // modify file outside of editor to simulate external save (no buffer backing | ||
| // the file now) | ||
| Path p = Path.of(testFile.getLocationURI()); | ||
| Files.writeString(p, "external-change", StandardCharsets.UTF_8); | ||
|
|
||
| // With fallback disabled, the mock server should NOT receive a didSave via | ||
| // resource fallback. | ||
| // Wait a short time and assert the future has not completed. | ||
| try { | ||
| didSaveExpectation.get(500, TimeUnit.MILLISECONDS); | ||
| throw new AssertionError("didSave should not have been received when fallback is disabled"); | ||
| } catch (TimeoutException expected) { | ||
| // expected: no didSave delivered | ||
| } | ||
|
|
||
| TestUtils.closeEditor(editor, false); | ||
| } | ||
| } |
93 changes: 93 additions & 0 deletions
93
...clipse.lsp4e.test/src/org/eclipse/lsp4e/test/edit/LSPEclipseUtilsNonBufferEditorTest.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,93 @@ | ||
| /******************************************************************************* | ||
| * Copyright (c) 2026 Advantest Europe GmbH and others. | ||
| * | ||
| * This program and the accompanying materials | ||
| * are made available under the terms of the Eclipse Public License 2.0 | ||
| * which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-2.0/ | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Contributors: | ||
| * Raghunandana Murthappa | ||
| *******************************************************************************/ | ||
| package org.eclipse.lsp4e.test.edit; | ||
|
|
||
| import static org.junit.jupiter.api.Assertions.assertEquals; | ||
| import static org.junit.jupiter.api.Assertions.assertNotNull; | ||
|
|
||
| import java.net.URI; | ||
|
|
||
| import org.eclipse.jface.preference.IPreferenceStore; | ||
| import org.eclipse.jface.text.Document; | ||
| import org.eclipse.jface.text.IDocument; | ||
| import org.eclipse.lsp4e.LSPEclipseUtils; | ||
| import org.eclipse.lsp4e.LanguageServerPlugin; | ||
| import org.eclipse.swt.widgets.Display; | ||
| import org.eclipse.ui.IEditorPart; | ||
| import org.eclipse.ui.PlatformUI; | ||
| import org.junit.jupiter.api.Test; | ||
|
|
||
| /** | ||
| * Integration-like test that opens an editor whose document is not connected to | ||
| * FileBuffers and verifies LSPEclipseUtils.toUri(IDocument) uses adapters | ||
| * fallback. | ||
| */ | ||
| public class LSPEclipseUtilsNonBufferEditorTest { | ||
|
|
||
| private static final URI TEST_URI = URI.create("nonbuffer://test/doc"); | ||
|
|
||
| /** | ||
| * This document can be adapted to a URI directly. | ||
| */ | ||
| private static final class TestDocument extends Document implements org.eclipse.core.runtime.IAdaptable { | ||
| private final URI uri; | ||
|
|
||
| TestDocument(URI uri) { | ||
| super(); | ||
| this.uri = uri; | ||
| } | ||
|
|
||
| @Override | ||
| public <T> T getAdapter(Class<T> adapter) { | ||
| if (adapter == URI.class) { | ||
| @SuppressWarnings("unchecked") T t = (T) uri; | ||
| return t; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
|
|
||
| @Test | ||
| public void testNonBufferEditorDocumentToUri() throws Exception { | ||
| IPreferenceStore store = LanguageServerPlugin.getDefault().getPreferenceStore(); | ||
| store.setValue("org.eclipse.lsp4e.resourceFallback.enabled", true); | ||
| // Create a non-buffer-managed document adapted to this URI directly so the test | ||
| // can run without an Eclipse workbench (headless/unit test environments). | ||
| IDocument directDoc = new TestDocument(TEST_URI); | ||
| URI resolvedDirect = LSPEclipseUtils.toUri(directDoc); | ||
| assertEquals(TEST_URI, resolvedDirect, "toUri should resolve the adapter-provided URI"); | ||
|
|
||
| // If a workbench is available, also exercise opening the editor to verify the | ||
| // integration path. This part is optional and guarded so it won't fail the test | ||
| // when running in plain unit-test mode. | ||
| NonBufferEditorInput input = new NonBufferEditorInput(TEST_URI); | ||
| if (PlatformUI.isWorkbenchRunning()) { | ||
| Display.getDefault().syncExec(() -> { | ||
| try { | ||
| IEditorPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage() | ||
| .openEditor(input, "org.eclipse.lsp4e.test.edit.nonBufferEditor"); | ||
| assertNotNull(part); | ||
| if (part instanceof NonBufferEditor nbe) { | ||
| IDocument editorDoc = nbe.getDocument(); | ||
| assertNotNull(editorDoc, "Editor should have created a document"); | ||
| URI resolved = LSPEclipseUtils.toUri(editorDoc); | ||
| assertEquals(TEST_URI, resolved, "toUri should resolve the adapter-provided URI"); | ||
| } | ||
| } catch (Exception e) { | ||
| throw new RuntimeException(e); | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| } |
55 changes: 55 additions & 0 deletions
55
org.eclipse.lsp4e.test/src/org/eclipse/lsp4e/test/edit/NonBufferDocumentProvider.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| /******************************************************************************* | ||
| * Copyright (c) 2026 Advantest Europe GmbH and others. | ||
| * | ||
| * This program and the accompanying materials | ||
| * are made available under the terms of the Eclipse Public License 2.0 | ||
| * which accompanies this distribution, and is available at | ||
| * https://www.eclipse.org/legal/epl-2.0/ | ||
| * | ||
| * SPDX-License-Identifier: EPL-2.0 | ||
| * | ||
| * Contributors: | ||
| * Raghunandana Murthappa | ||
| *******************************************************************************/ | ||
| package org.eclipse.lsp4e.test.edit; | ||
|
|
||
| import java.net.URI; | ||
|
|
||
| import org.eclipse.jface.text.Document; | ||
| import org.eclipse.jface.text.IDocument; | ||
| import org.eclipse.ui.IEditorInput; | ||
|
|
||
| /** | ||
| * A fake provider that does not register the document with the FileBuffers. It | ||
| * can create an IDocument adapted to a URI for inputs of type | ||
| * {@link NonBufferEditorInput}. | ||
| */ | ||
| public class NonBufferDocumentProvider { | ||
|
|
||
| public static IDocument createDocument(IEditorInput input) { | ||
| if (input instanceof NonBufferEditorInput nbi) { | ||
| URI uri = nbi.getUri(); | ||
| return new DocWithAdapter(uri); | ||
| } | ||
| return new Document(); | ||
| } | ||
|
|
||
| private static final class DocWithAdapter extends Document implements org.eclipse.core.runtime.IAdaptable { | ||
| private static final long serialVersionUID = 1L; | ||
| private final URI uri; | ||
|
|
||
| DocWithAdapter(URI uri) { | ||
| super(); | ||
| this.uri = uri; | ||
| } | ||
|
|
||
| @Override | ||
| public <T> T getAdapter(Class<T> adapter) { | ||
| if (adapter == URI.class) { | ||
| @SuppressWarnings("unchecked") T t = (T) uri; | ||
| return t; | ||
| } | ||
| return null; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.