diff options
author | Vladimir Piskarev | 2018-05-04 10:58:28 +0000 |
---|---|---|
committer | Vladimir Piskarev | 2018-05-04 12:41:40 +0000 |
commit | 19df8ef3539636a7c2d1e21ff1784a74df9652ed (patch) | |
tree | 97ff6b2bb5b1c4578369c13e854754ce155626b7 | |
parent | 14e4b06cbf737403e3e45c670073aaac60e06b01 (diff) | |
download | org.eclipse.handly-19df8ef3539636a7c2d1e21ff1784a74df9652ed.tar.gz org.eclipse.handly-19df8ef3539636a7c2d1e21ff1784a74df9652ed.tar.xz org.eclipse.handly-19df8ef3539636a7c2d1e21ff1784a74df9652ed.zip |
Bug 530940 - Provide an LSP example
Additional work on the LSP example.
8 files changed, 404 insertions, 71 deletions
diff --git a/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/DeltaProcessorTest.java b/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/DeltaProcessorTest.java new file mode 100644 index 00000000..48227357 --- /dev/null +++ b/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/DeltaProcessorTest.java @@ -0,0 +1,268 @@ +/******************************************************************************* + * Copyright (c) 2018 1C-Soft LLC. + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vladimir Piskarev (1C) - initial API and implementation + *******************************************************************************/ +package org.eclipse.handly.internal.examples.lsp; + +import java.io.ByteArrayInputStream; +import java.util.Arrays; +import java.util.Comparator; + +import org.eclipse.core.resources.IMarker; +import org.eclipse.core.resources.IMarkerDelta; +import org.eclipse.core.resources.IProject; +import org.eclipse.core.resources.IResourceDelta; +import org.eclipse.core.runtime.Path; +import org.eclipse.handly.examples.lsp.LanguageCore; +import org.eclipse.handly.junit.WorkspaceTestCase; +import org.eclipse.handly.model.ElementDeltas; +import org.eclipse.handly.model.Elements; +import org.eclipse.handly.model.IElementChangeEvent; +import org.eclipse.handly.model.IElementChangeListener; +import org.eclipse.handly.model.IElementDelta; + +/** + * <code>DeltaProcessor</code> tests. + */ +public class DeltaProcessorTest + extends WorkspaceTestCase +{ + private IProject project; + private LanguageSourceFile aFile; + private ElementChangeListener listener; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + project = setUpProject("Test001"); + aFile = new LanguageSourceFile(null, project.getFile("a.foo")); + listener = new ElementChangeListener(); + LanguageCore.addElementChangeListener(listener); + } + + @Override + protected void tearDown() throws Exception + { + LanguageCore.removeElementChangeListener(listener); + super.tearDown(); + } + + public void test01() throws Exception + { + aFile.getBody_(); // ensure element is open + + ServerWrapper serverWrapper = aFile.serverManager().getServerWrapper( + aFile); + assertTrue(serverWrapper.hasConnection()); + + project.delete(true, null); + + assertFalse(serverWrapper.hasConnection()); + + assertDeltas("[/Test001/a.foo[-]: {}]"); + assertFalse(aFile.exists()); + + setUpProject(project.getName()); // re-create project + + assertDeltas("[/Test001/a.foo[+]: {}]"); + assertTrue(aFile.exists()); + } + + public void test02() throws Exception + { + aFile.getBody_(); // ensure element is open + + ServerWrapper serverWrapper = aFile.serverManager().getServerWrapper( + aFile); + assertTrue(serverWrapper.hasConnection()); + + project.close(null); + + assertFalse(serverWrapper.hasConnection()); + + assertDeltas("[/Test001/a.foo[-]: {}]"); + assertFalse(aFile.exists()); + + project.open(null); + + assertDeltas("[/Test001/a.foo[+]: {}]"); + assertTrue(aFile.exists()); + } + + public void test03() throws Exception + { + aFile.getBody_(); // ensure element is open + + ServerWrapper serverWrapper = aFile.serverManager().getServerWrapper( + aFile); + assertTrue(serverWrapper.hasConnection()); + + project.move(new Path("/foo"), true, null); + + assertFalse(serverWrapper.hasConnection()); + + assertDeltas("[/Test001/a.foo[-]: {MOVED_TO(/foo/a.foo)}, " + + "/foo/a.foo[+]: {MOVED_FROM(/Test001/a.foo)}]"); + assertFalse(aFile.exists()); + assertTrue(Elements.exists(ElementDeltas.getElement( + listener.deltas[1]))); + } + + public void test04() throws Exception + { + aFile.getBody_(); // ensure element is open + + aFile.getFile().touch(null); + + assertDeltas("[/Test001/a.foo[*]: {CONTENT}]"); + assertNull(aFile.findBody_()); // assert element is closed + } + + public void test05() throws Exception + { + IMarker marker = aFile.getFile().createMarker(IMarker.PROBLEM); + + assertDeltas("[/Test001/a.foo[*]: {MARKERS}]"); + IMarkerDelta[] markerDeltas = ElementDeltas.getMarkerDeltas( + listener.deltas[0]); + assertEquals(1, markerDeltas.length); + IMarkerDelta markerDelta = markerDeltas[0]; + assertEquals(IResourceDelta.ADDED, markerDelta.getKind()); + assertEquals(marker, markerDelta.getMarker()); + } + + public void test06() throws Exception + { + aFile.becomeWorkingCopy(null); + try + { + Object body = aFile.findBody_(); + assertNotNull(body); + + aFile.getFile().delete(true, null); + + assertDeltas("[/Test001/a.foo[-]: {UNDERLYING RESOURCE}]"); + assertSame(body, aFile.findBody_()); + } + finally + { + aFile.releaseWorkingCopy(); + } + } + + public void test07() throws Exception + { + aFile.getFile().delete(true, null); + aFile.becomeWorkingCopy(null); + try + { + Object body = aFile.findBody_(); + assertNotNull(body); + + aFile.getFile().create(new ByteArrayInputStream(new byte[0]), true, + null); + + assertDeltas("[/Test001/a.foo[+]: {UNDERLYING RESOURCE}]"); + assertSame(body, aFile.findBody_()); + } + finally + { + aFile.releaseWorkingCopy(); + } + } + + public void test08() throws Exception + { + aFile.becomeWorkingCopy(null); + try + { + Object body = aFile.findBody_(); + assertNotNull(body); + + aFile.getFile().move(new Path("b/a.foo"), true, null); + + assertDeltas( + "[/Test001/a.foo[-]: {MOVED_TO(/Test001/b/a.foo) | UNDERLYING RESOURCE}, " + + "/Test001/b/a.foo[+]: {MOVED_FROM(/Test001/a.foo)}]"); + assertSame(body, aFile.findBody_()); + assertTrue(Elements.exists(ElementDeltas.getElement( + listener.deltas[1]))); + } + finally + { + aFile.releaseWorkingCopy(); + } + } + + public void test09() throws Exception + { + aFile.becomeWorkingCopy(null); + try + { + Object body = aFile.findBody_(); + assertNotNull(body); + + aFile.getFile().touch(null); + + assertDeltas( + "[/Test001/a.foo[*]: {CONTENT | UNDERLYING RESOURCE}]"); + assertSame(body, aFile.findBody_()); + } + finally + { + aFile.releaseWorkingCopy(); + } + } + + public void test10() throws Exception + { + aFile.becomeWorkingCopy(null); + try + { + Object body = aFile.findBody_(); + assertNotNull(body); + + IMarker marker = aFile.getFile().createMarker(IMarker.PROBLEM); + + assertDeltas( + "[/Test001/a.foo[*]: {UNDERLYING RESOURCE | MARKERS}]"); + IMarkerDelta[] markerDeltas = ElementDeltas.getMarkerDeltas( + listener.deltas[0]); + assertEquals(1, markerDeltas.length); + IMarkerDelta markerDelta = markerDeltas[0]; + assertEquals(IResourceDelta.ADDED, markerDelta.getKind()); + assertEquals(marker, markerDelta.getMarker()); + } + finally + { + aFile.releaseWorkingCopy(); + } + } + + private void assertDeltas(String expected) + { + Arrays.sort(listener.deltas, Comparator.comparing(Object::toString)); + assertEquals(expected, Arrays.toString(listener.deltas)); + } + + private static class ElementChangeListener + implements IElementChangeListener + { + IElementDelta[] deltas; + + @Override + public void elementChanged(IElementChangeEvent event) + { + deltas = event.getDeltas(); + } + } +} diff --git a/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFileTestBase.java b/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFileTestBase.java index 2253267f..b119a881 100644 --- a/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFileTestBase.java +++ b/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFileTestBase.java @@ -150,8 +150,8 @@ abstract class LanguageSourceFileTestBase abstract ServerManager getServerManager(); - private void assertBody(LanguageSourceElement element, TextRange fullRange, - LanguageSymbol... children) throws CoreException + private static void assertBody(LanguageSourceElement element, + TextRange fullRange, LanguageSymbol... children) throws CoreException { SourceElementBody body = (SourceElementBody)element.getBody_(); assertEquals(fullRange, body.getFullRange()); diff --git a/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/ServerManagerTest.java b/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/ServerManagerTest.java new file mode 100644 index 00000000..24a33182 --- /dev/null +++ b/org.eclipse.handly.examples.lsp.tests/src/org/eclipse/handly/internal/examples/lsp/ServerManagerTest.java @@ -0,0 +1,77 @@ +/******************************************************************************* + * Copyright (c) 2018 1C-Soft LLC. + * + * This program and the accompanying materials are made available under + * the terms of the Eclipse Public License 2.0 which is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Vladimir Piskarev (1C) - initial API and implementation + *******************************************************************************/ +package org.eclipse.handly.internal.examples.lsp; + +import org.eclipse.core.resources.IProject; +import org.eclipse.core.runtime.Path; +import org.eclipse.handly.examples.lsp.ILanguageSourceFile; +import org.eclipse.handly.examples.lsp.LanguageCore; +import org.eclipse.handly.junit.WorkspaceTestCase; + +/** + * <code>ServerManager</code> tests. + */ +public class ServerManagerTest + extends WorkspaceTestCase +{ + private static final int MAX_CONNECTIONS = ServerManager.MAX_CONNECTIONS; + + @Override + protected void setUp() throws Exception + { + super.setUp(); + IProject project = setUpProject("Test001"); + for (int i = 0; i <= MAX_CONNECTIONS; i++) + { + project.copy(new Path("p" + i), true, null); + } + project.delete(true, null); + } + + public void test1() throws Exception + { + ILanguageSourceFile[] files = new ILanguageSourceFile[MAX_CONNECTIONS + + 1]; + + for (int i = 0; i < MAX_CONNECTIONS; i++) + { + ILanguageSourceFile aFile = LanguageCore.createSourceFileFrom( + getProject("p" + i).getFile("a.foo")); + aFile.getSymbols(null); // ensure element is open + files[i] = aFile; + } + + for (int i = 0; i < MAX_CONNECTIONS; i++) + { + assertTrue( + ModelManager.INSTANCE.getServerManager().getServerWrapper( + files[i]).hasConnection()); + } + + ILanguageSourceFile aFile = LanguageCore.createSourceFileFrom( + getProject("p" + MAX_CONNECTIONS).getFile("a.foo")); + aFile.getSymbols(null); // ensure element is open + files[MAX_CONNECTIONS] = aFile; + + for (int i = 0; i <= MAX_CONNECTIONS; i++) + { + boolean hasConnection = + ModelManager.INSTANCE.getServerManager().getServerWrapper( + files[i]).hasConnection(); + if (i == 0) + assertFalse(hasConnection); + else + assertTrue(hasConnection); + } + } +} diff --git a/org.eclipse.handly.examples.lsp.tests/workspace/Test001/b/file.txt b/org.eclipse.handly.examples.lsp.tests/workspace/Test001/b/file.txt new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/org.eclipse.handly.examples.lsp.tests/workspace/Test001/b/file.txt diff --git a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/DeltaProcessor.java b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/DeltaProcessor.java index 6682b796..ad48d39f 100644 --- a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/DeltaProcessor.java +++ b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/DeltaProcessor.java @@ -13,6 +13,7 @@ package org.eclipse.handly.internal.examples.lsp; import static org.eclipse.handly.model.IElementDeltaConstants.ADDED; +import static org.eclipse.handly.model.IElementDeltaConstants.CHANGED; import static org.eclipse.handly.model.IElementDeltaConstants.F_CONTENT; import static org.eclipse.handly.model.IElementDeltaConstants.F_MARKERS; import static org.eclipse.handly.model.IElementDeltaConstants.F_MOVED_FROM; @@ -86,7 +87,6 @@ final class DeltaProcessor & IResourceDelta.OPEN) != 0) && !project.isOpen()) { ModelManager.INSTANCE.getServerManager().disconnect(project); - return false; } return true; } @@ -119,18 +119,14 @@ final class DeltaProcessor ModelManager.INSTANCE.getServerManager().updateUri( (ILanguageSourceFile)element); + long flags = 0; + if (!isWorkingCopy(element)) - { addToModel(element); - translateAddedDelta(delta, element); - } else - { - LanguageElementDelta result = new LanguageElementDelta(element); - result.setKind(ADDED); - result.setFlags(F_UNDERLYING_RESOURCE); - deltas.add(result); - } + flags = F_UNDERLYING_RESOURCE; + + translateAddedDelta(delta, element, flags); } return false; } @@ -141,18 +137,14 @@ final class DeltaProcessor ILanguageElement element = LanguageCore.create(file); if (element != null) { + long flags = 0; + if (!isWorkingCopy(element)) - { removeFromModel(element); - translateRemovedDelta(delta, element); - } else - { - LanguageElementDelta result = new LanguageElementDelta(element); - result.setKind(REMOVED); - result.setFlags(F_UNDERLYING_RESOURCE); - deltas.add(result); - } + flags = F_UNDERLYING_RESOURCE; + + translateRemovedDelta(delta, element, flags); } return false; } @@ -164,6 +156,7 @@ final class DeltaProcessor if (element != null) { LanguageElementDelta result = new LanguageElementDelta(element); + result.setKind(CHANGED); long flags = 0; @@ -213,7 +206,7 @@ final class DeltaProcessor } private void translateAddedDelta(IResourceDelta delta, - ILanguageElement element) + ILanguageElement element, long flags) { LanguageElementDelta result = new LanguageElementDelta(element); result.setKind(ADDED); @@ -223,15 +216,16 @@ final class DeltaProcessor delta.getMovedFromPath(), delta.getResource().getType())); if (movedFromElement != null) { - result.setFlags(F_MOVED_FROM); + flags |= F_MOVED_FROM; result.setMovedFromElement(movedFromElement); } } + result.setFlags(flags); deltas.add(result); } private void translateRemovedDelta(IResourceDelta delta, - ILanguageElement element) + ILanguageElement element, long flags) { LanguageElementDelta result = new LanguageElementDelta(element); result.setKind(REMOVED); @@ -241,10 +235,11 @@ final class DeltaProcessor delta.getMovedToPath(), delta.getResource().getType())); if (movedToElement != null) { - result.setFlags(F_MOVED_TO); + flags |= F_MOVED_TO; result.setMovedToElement(movedToElement); } } + result.setFlags(flags); deltas.add(result); } diff --git a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFile.java b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFile.java index 08bf4ec7..c1058147 100644 --- a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFile.java +++ b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/LanguageSourceFile.java @@ -16,7 +16,6 @@ import static org.eclipse.handly.context.Contexts.EMPTY_CONTEXT; import static org.eclipse.handly.context.Contexts.of; import static org.eclipse.handly.model.Elements.BASE_SNAPSHOT; -import java.net.URI; import java.util.List; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -24,8 +23,6 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import org.eclipse.core.filesystem.EFS; -import org.eclipse.core.filesystem.IFileStore; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IResource; import org.eclipse.core.runtime.CoreException; @@ -149,14 +146,11 @@ class LanguageSourceFile if (isWorkingCopy_()) throw new AssertionError(); serverManager().close(this); // workaround for race with releaseWorkingCopy()_ - long fileStamp; - do + try (ISnapshotProvider provider = getFileSnapshotProvider_()) { - fileStamp = fileStamp(); - NonExpiringSnapshot fileSnapshot; - try ( - ISnapshotProvider provider = getFileSnapshotProvider_()) + do { + NonExpiringSnapshot fileSnapshot; try { fileSnapshot = new NonExpiringSnapshot(provider); @@ -169,12 +163,12 @@ class LanguageSourceFile throw new CoreException(Activator.createErrorStatus( e.getMessage(), e)); } + symbols = symbols(monitor); + source = fileSnapshot.getContents(); + snapshot = fileSnapshot.getWrappedSnapshot(); } - symbols = symbols(monitor); - source = fileSnapshot.getContents(); - snapshot = fileSnapshot.getWrappedSnapshot(); + while (!snapshot.isEqualTo(provider.getSnapshot())); } - while (fileStamp != fileStamp()); } } @@ -193,6 +187,12 @@ class LanguageSourceFile return true; } + @Override + public void toStringName_(StringBuilder builder, IContext context) + { + builder.append(file.getFullPath()); + } + ServerManager serverManager() { return getModelManager_().getServerManager(); @@ -217,24 +217,6 @@ class LanguageSourceFile } } - private long fileStamp() - { - URI uri = file.getLocationURI(); - if (uri == null) - return EFS.NONE; - IFileStore fileStore; - try - { - fileStore = EFS.getStore(uri); - } - catch (CoreException e) - { - Activator.log(e.getStatus()); - return EFS.NONE; - } - return fileStore.fetchInfo().getLastModified(); - } - private class LspReconcileOperation extends NotifyingReconcileOperation { diff --git a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerManager.java b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerManager.java index cd105521..d45ddb36 100644 --- a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerManager.java +++ b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerManager.java @@ -56,7 +56,10 @@ import org.eclipse.lsp4j.services.LanguageServer; @SuppressWarnings("restriction") class ServerManager { - private static final int MAX_CONNECTIONS = 16; + /** + * Should have been private. Package-private for testing purposes only. + */ + static final int MAX_CONNECTIONS = 16; static final ExecutorService THREAD_POOL = Executors.newCachedThreadPool(); @@ -228,7 +231,7 @@ class ServerManager } /** - * Should have been private. Package-private to enable mocking in tests. + * Should have been private. Package-private for testing purposes only. * * @param sourceFile not <code>null</code> * @return the server wrapper, or <code>null</code> @@ -339,8 +342,8 @@ class ServerManager } }; LanguageServer[] serverSlot = new LanguageServer[1]; - Launcher<? extends LanguageServer> launcher = Launcher.createLauncher( - client, LanguageServer.class, streamProvider.getInputStream(), + Launcher<LanguageServer> launcher = Launcher.createLauncher(client, + LanguageServer.class, streamProvider.getInputStream(), streamProvider.getOutputStream(), THREAD_POOL, consumer -> (message -> { diff --git a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerWrapper.java b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerWrapper.java index b61423de..ddff505a 100644 --- a/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerWrapper.java +++ b/org.eclipse.handly.examples.lsp/src/org/eclipse/handly/internal/examples/lsp/ServerWrapper.java @@ -39,7 +39,7 @@ import org.eclipse.lsp4j.services.TextDocumentService; /** * A high-level wrapper for the language server. Hides aspects such as - * obtaining and restoring a connection to the server when necessary. + * on-demand obtaining/restoring a connection to the server. */ class ServerWrapper { @@ -85,6 +85,17 @@ class ServerWrapper } /** + * For testing purposes only. + * + * @return <code>true</code> if the connection is currently open, and + * <code>false</code> otherwise + */ + boolean hasConnection() + { + return connection() != null; + } + + /** * Returns the capabilities the underlying server provides. * * @return the capabilities future (never <code>null</code>) @@ -150,16 +161,13 @@ class ServerWrapper ServerConnection c = connection(); if (c == null || !c.status.getAsBoolean()) return; - if (uri != null && uri.equals(oldUri)) - { - VersionedTextDocumentIdentifier id = - new VersionedTextDocumentIdentifier(info.getVersion()); - id.setUri(uri.toString()); - c.server.getTextDocumentService().didChange( - new DidChangeTextDocumentParams(id, - Collections.singletonList( - new TextDocumentContentChangeEvent(newText)))); - } + VersionedTextDocumentIdentifier id = + new VersionedTextDocumentIdentifier(info.getVersion()); + id.setUri(uri.toString()); + c.server.getTextDocumentService().didChange( + new DidChangeTextDocumentParams(id, + Collections.singletonList( + new TextDocumentContentChangeEvent(newText)))); } else { |