diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java index 5436ee728..acc1a9e2b 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServer.java @@ -10,6 +10,7 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java; +import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.CompletableFuture; @@ -17,6 +18,8 @@ import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.CompletionItemKind; import org.eclipse.lsp4j.InitializeParams; import org.eclipse.lsp4j.InitializeResult; +import org.eclipse.lsp4j.Registration; +import org.eclipse.lsp4j.RegistrationParams; import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyAwareFactoryManager; import org.springframework.ide.vscode.boot.java.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; @@ -56,6 +59,7 @@ import org.springframework.ide.vscode.commons.languageserver.completion.IComplet import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFoldersProposedService; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; import org.springframework.ide.vscode.commons.languageserver.util.LSFactory; import org.springframework.ide.vscode.commons.languageserver.util.ReferencesHandler; @@ -174,13 +178,17 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { public CompletableFuture initialize(InitializeParams params) { CompletableFuture result = super.initialize(params); - this.indexer.initialize(this.getWorkspaceRoot()); + this.indexer.initialize(getWorkspaceRoots()); return result; } @Override public void initialized() { + Registration registration = new Registration(WorkspaceFoldersProposedService.CAPABILITY_ID, WorkspaceFoldersProposedService.CAPABILITY_NAME, null); + RegistrationParams registrationParams = new RegistrationParams(Collections.singletonList(registration)); + getClient().registerCapability(registrationParams); + // TODO: due to a missing message from lsp4e this "initialized" is not called in // the LSP4E case // if this gets fixed, the code should move here (from "initialize" above) @@ -360,4 +368,5 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { public CompilationUnitCache getCompilationUnitCache() { return cuCache; } + } diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java index 721f5add3..0951d80e9 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/BootJavaLanguageServerParams.java @@ -15,13 +15,13 @@ import java.time.Duration; import java.util.Arrays; import org.springframework.ide.vscode.boot.java.handlers.RunningAppProvider; -import org.springframework.ide.vscode.boot.java.utils.BootProjectUtil; import org.springframework.ide.vscode.boot.java.utils.SpringLiveHoverWatchdog; import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; import org.springframework.ide.vscode.commons.gradle.GradleCore; import org.springframework.ide.vscode.commons.gradle.GradleProjectCache; import org.springframework.ide.vscode.commons.gradle.GradleProjectFinder; +import org.springframework.ide.vscode.commons.java.BootProjectUtil; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.CompositeProjectOvserver; diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index f164d1e45..15b79c0f4 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java @@ -54,6 +54,7 @@ import org.springframework.ide.vscode.commons.java.IClasspath; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver.Listener; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.StringUtil; import org.springframework.ide.vscode.commons.util.text.LanguageId; @@ -105,12 +106,13 @@ public class SpringIndexer { this.symbols = Collections.synchronizedList(new ArrayList<>()); this.symbolsByDoc = new ConcurrentHashMap<>(); + server.getWorkspaceService().onDidChangeWorkspaceFolders(evt -> { + System.err.println("Workspace roots have changed!"); + refresh(); + }); } - public CompletableFuture initialize(final Path workspaceRoot) { - if (workspaceRoot==null) { - return CompletableFuture.completedFuture(null); - } + public CompletableFuture initialize(Collection workspaceRoots) { synchronized(this) { if (this.initializeTask == null) { initializing.set(true); @@ -120,7 +122,9 @@ public class SpringIndexer { this.initializeTask = CompletableFuture.runAsync(new Runnable() { @Override public void run() { - scanFiles(workspaceRoot.toFile()); + for (WorkspaceFolder root : workspaceRoots) { + scanFiles(root); + } SpringIndexer.this.updateQueue = new LinkedBlockingQueue<>(); SpringIndexer.this.updateWorker = new Thread(new Runnable() { @@ -141,13 +145,12 @@ public class SpringIndexer { } } }, "Spring Annotation Index Update Worker"); - updateWorker.start(); initializing.set(false); } + }); } - return this.initializeTask; } } @@ -163,7 +166,7 @@ public class SpringIndexer { symbols.clear(); symbolsByDoc.clear(); Log.info("Rebuilding SpringIndexer..."); - initialize(server.getWorkspaceRoot()); + initialize(server.getWorkspaceRoots()); } } @@ -252,9 +255,9 @@ public class SpringIndexer { return null; } - private void scanFiles(File directory) { + private void scanFiles(WorkspaceFolder directory) { try { - Map, List> projects = Files.walk(directory.toPath()) + Map, List> projects = Files.walk(Paths.get(new URI(directory.getUri()))) .filter(path -> path.getFileName().toString().endsWith(".java")) .filter(Files::isRegularFile) .map(path -> path.toAbsolutePath().toString()) @@ -268,6 +271,7 @@ public class SpringIndexer { } private void scanProject(IJavaProject project, String[] files) { + System.err.println("scan project: "+project); try { ASTParser parser = ASTParser.newParser(AST.JLS8); String[] classpathEntries = getClasspathEntries(project); diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java index 8b015cad9..20bbae458 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java +++ b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/value/ValuePropertyReferencesProvider.java @@ -34,6 +34,7 @@ import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.Position; import org.eclipse.lsp4j.Range; import org.springframework.ide.vscode.boot.java.handlers.ReferenceProvider; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.util.BadLocationException; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -94,7 +95,7 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider { if (range != null) { String propertyKey = value.substring(range.getStart(), range.getEnd()); if (propertyKey != null && propertyKey.length() > 0) { - return findReferencesFromPropertyFiles(languageServer.getWorkspaceRoot(), propertyKey); + return findReferencesFromPropertyFiles(languageServer.getWorkspaceRoots(), propertyKey); } } } @@ -105,21 +106,26 @@ public class ValuePropertyReferencesProvider implements ReferenceProvider { return null; } - public CompletableFuture> findReferencesFromPropertyFiles(Path workspaceRoot, - String propertyKey) { + public CompletableFuture> findReferencesFromPropertyFiles( + Collection workspaceRoots, + String propertyKey + ) { + for (WorkspaceFolder workspaceFolder : workspaceRoots) { + try { + Path workspaceRoot = Paths.get(new URI(workspaceFolder.getUri())); + try (Stream walk = Files.walk(workspaceRoot)) { + List locations = walk + .filter(path -> isPropertiesFile(path)) + .filter(path -> path.toFile().isFile()) + .map(path -> findReferences(path, propertyKey)) + .flatMap(Collection::stream) + .collect(Collectors.toList()); - try (Stream walk = Files.walk(workspaceRoot)) { - List locations = walk - .filter(path -> isPropertiesFile(path)) - .filter(path -> path.toFile().isFile()) - .map(path -> findReferences(path, propertyKey)) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - - return CompletableFuture.completedFuture(locations); - } - catch (Exception e) { - e.printStackTrace(); + return CompletableFuture.completedFuture(locations); + } + } catch (Exception e) { + e.printStackTrace(); + } } return null; diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java index 4a4c14944..48a50628e 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerBeansTest.java @@ -14,6 +14,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import java.io.File; +import java.util.Collection; import java.util.HashMap; import java.util.Iterator; import java.util.List; @@ -30,9 +31,12 @@ import org.springframework.ide.vscode.boot.java.beans.test.SpringIndexerHarness. import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import com.google.common.collect.ImmutableList; + /** * @author Martin Lippert */ @@ -57,7 +61,7 @@ public class SpringIndexerBeansTest { public void testScanSimpleConfigurationClass() throws Exception { SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(indexer.wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java", @@ -71,7 +75,7 @@ public class SpringIndexerBeansTest { @Test public void testScanSpecialConfigurationClass() throws Exception { SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(indexer.wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); String docUri = uriPrefix + "/src/main/java/org/test/SpecialConfiguration.java"; @@ -103,7 +107,7 @@ public class SpringIndexerBeansTest { public void testScanSimpleFunctionBean() throws Exception { SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(indexer.wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/FunctionClass.java", @@ -119,7 +123,7 @@ public class SpringIndexerBeansTest { public void testScanSimpleComponentClass() throws Exception { SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(indexer.wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); indexer.assertDocumentSymbols(uriPrefix + "/src/main/java/org/test/SimpleComponent.java", @@ -134,7 +138,7 @@ public class SpringIndexerBeansTest { @Test public void testScanSimpleControllerClass() throws Exception { SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(indexer.wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); String docUri = uriPrefix + "/src/main/java/org/test/SimpleController.java"; @@ -148,7 +152,7 @@ public class SpringIndexerBeansTest { @Test public void testScanRestControllerClass() throws Exception { SpringIndexerHarness indexer = new SpringIndexerHarness(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(indexer.wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); String docUri = uriPrefix + "/src/main/java/org/test/SimpleRestController.java"; diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java index 8064b7252..141e3afdd 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/beans/test/SpringIndexerHarness.java @@ -12,14 +12,14 @@ package org.springframework.ide.vscode.boot.java.beans.test; import static org.junit.Assert.assertEquals; +import java.io.File; import java.net.URI; -import java.nio.file.Path; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.List; -import java.util.Map; import org.apache.commons.io.IOUtils; import org.eclipse.lsp4j.Range; @@ -29,6 +29,7 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyA import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider; import org.springframework.ide.vscode.boot.java.utils.SpringIndexer; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; import org.springframework.ide.vscode.commons.util.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; import org.springframework.ide.vscode.languageserver.testharness.Editor; @@ -139,7 +140,17 @@ public class SpringIndexerHarness { return ImmutableList.of(); } - public void initialize(Path wsRoot) { - indexer.initialize(wsRoot); + public Collection wsFolder(File directory) { + if (directory!=null) { + return ImmutableList.of(new WorkspaceFolder( + directory.toURI().toString(), + directory.getName() + )); + } + return ImmutableList.of(); + } + + public void initialize(Collection wsRoots) { + indexer.initialize(wsRoots); } } diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java index 3ffec6073..4ac7509e2 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/references/test/PropertyReferenceFinderTest.java @@ -13,17 +13,22 @@ package org.springframework.ide.vscode.boot.java.references.test; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import java.io.File; import java.net.URI; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Collection; import java.util.List; import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.Location; import org.junit.Test; import org.springframework.ide.vscode.boot.java.value.ValuePropertyReferencesProvider; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; import org.springframework.ide.vscode.project.harness.ProjectsHarness; +import com.google.common.collect.ImmutableList; + /** * @author Martin Lippert */ @@ -34,7 +39,7 @@ public class PropertyReferenceFinderTest { ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(null); Path root = Paths.get(ProjectsHarness.class.getResource("/test-property-files/simple-case/").toURI()); - CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(root, "test.property"); + CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "test.property"); assertNotNull(resultFuture); List locations = resultFuture.get(); @@ -49,12 +54,22 @@ public class PropertyReferenceFinderTest { assertEquals(13, location.getRange().getEnd().getCharacter()); } + private Collection wsFolder(Path directory) { + if (directory!=null) { + return ImmutableList.of(new WorkspaceFolder( + directory.toUri().toString(), + directory.getFileName().toString() + )); + } + return ImmutableList.of(); + } + @Test public void testFindReferenceAtBeginningYMLFile() throws Exception { ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(null); Path root = Paths.get(ProjectsHarness.class.getResource("/test-property-files/simple-yml/").toURI()); - CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(root, "test.property"); + CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "test.property"); assertNotNull(resultFuture); List locations = resultFuture.get(); @@ -74,7 +89,7 @@ public class PropertyReferenceFinderTest { ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(null); Path root = Paths.get(ProjectsHarness.class.getResource("/test-property-files/simple-case/").toURI()); - CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(root, "server.port"); + CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "server.port"); assertNotNull(resultFuture); List locations = resultFuture.get(); @@ -94,7 +109,7 @@ public class PropertyReferenceFinderTest { ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(null); Path root = Paths.get(ProjectsHarness.class.getResource("/test-property-files/multiple-files/").toURI()); - CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(root, "appl1.prop"); + CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "appl1.prop"); assertNotNull(resultFuture); List locations = resultFuture.get(); @@ -121,14 +136,14 @@ public class PropertyReferenceFinderTest { assertEquals(1, location.getRange().getEnd().getLine()); assertEquals(10, location.getRange().getEnd().getCharacter()); } - + private Location getLocation(List locations, URI docURI) { for (Location location : locations) { if (docURI.toString().equals(location.getUri())) { return location; } } - + return null; } @@ -137,7 +152,7 @@ public class PropertyReferenceFinderTest { ValuePropertyReferencesProvider provider = new ValuePropertyReferencesProvider(null); Path root = Paths.get(ProjectsHarness.class.getResource("/test-property-files/mixed-multiple-files/").toURI()); - CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(root, "appl1.prop"); + CompletableFuture> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "appl1.prop"); assertNotNull(resultFuture); List locations = resultFuture.get(); diff --git a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringPropertyIndexTest.java b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringPropertyIndexTest.java index e2c9102cd..00ef3c865 100644 --- a/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringPropertyIndexTest.java +++ b/headless-services/boot-java-language-server/src/test/java/org/springframework/ide/vscode/boot/java/utils/test/SpringPropertyIndexTest.java @@ -48,7 +48,7 @@ public class SpringPropertyIndexTest { } @Test - public void testScanningAllAnnotationsSimpleProjectUpfront() throws Exception { + public void testPropertiesIndexRefreshOnProjectChange() throws Exception { harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-parent/test-annotation-indexing/").toURI())); propertyIndexProvider = (DefaultSpringPropertyIndexProvider) harness.getServer().getSpringPropertyIndexProvider(); diff --git a/headless-services/boot-properties-language-server/pom.xml b/headless-services/boot-properties-language-server/pom.xml index 845171fcc..2b1974590 100644 --- a/headless-services/boot-properties-language-server/pom.xml +++ b/headless-services/boot-properties-language-server/pom.xml @@ -80,6 +80,13 @@ ${dependencies.version} test + + + org.mockito + mockito-all + ${mockito-version} + test + diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServer.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServer.java index e0983655d..5bfa8c41b 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServer.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServer.java @@ -10,9 +10,6 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot; -import java.nio.file.Paths; -import java.util.Arrays; - import org.springframework.ide.vscode.boot.common.PropertyCompletionFactory; import org.springframework.ide.vscode.boot.common.RelaxedNameConfig; import org.springframework.ide.vscode.boot.metadata.PropertyInfo; @@ -23,24 +20,17 @@ import org.springframework.ide.vscode.boot.properties.hover.PropertiesHoverInfoP import org.springframework.ide.vscode.boot.properties.reconcile.SpringPropertiesReconcileEngine; import org.springframework.ide.vscode.boot.yaml.completions.ApplicationYamlAssistContext; import org.springframework.ide.vscode.boot.yaml.reconcile.ApplicationYamlReconcileEngine; -import org.springframework.ide.vscode.commons.gradle.GradleCore; -import org.springframework.ide.vscode.commons.gradle.GradleProjectCache; -import org.springframework.ide.vscode.commons.gradle.GradleProjectFinder; -import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.hover.HoverInfoProvider; import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.hover.VscodeHoverEngineAdapter.HoverType; -import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.java.CompositeProjectOvserver; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.languageserver.reconcile.IReconcileEngine; +import org.springframework.ide.vscode.commons.languageserver.util.LSFactory; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; -import org.springframework.ide.vscode.commons.maven.MavenCore; -import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache; -import org.springframework.ide.vscode.commons.maven.java.MavenProjectFinder; import org.springframework.ide.vscode.commons.util.FuzzyMap; import org.springframework.ide.vscode.commons.util.text.IDocument; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -72,8 +62,8 @@ public class BootPropertiesLanguageServer extends SimpleLanguageServer { public boolean includeDeindentedProposals() { return false; }; }; // Shared: - private final CompositeProjectOvserver projectObserver; - private final CompositeJavaProjectFinder javaProjectFinder; + private final ProjectObserver projectObserver; + private final JavaProjectFinder javaProjectFinder; private final SpringPropertyIndexProvider indexProvider; private final TypeUtilProvider typeUtilProvider; private final VscodeCompletionEngineAdapter completionEngine; @@ -88,11 +78,16 @@ public class BootPropertiesLanguageServer extends SimpleLanguageServer { private final YamlStructureProvider yamlStructureProvider= YamlStructureProvider.DEFAULT; private YamlAssistContextProvider yamlAssistContextProvider; - public BootPropertiesLanguageServer(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider, CompositeJavaProjectFinder javaProjectFinder) { + public BootPropertiesLanguageServer(LSFactory _params) { super("vscode-boot-properties"); - this.indexProvider = indexProvider; - this.typeUtilProvider = typeUtilProvider; - this.javaProjectFinder = javaProjectFinder; + + BootPropertiesLanguageServerParams serverParams = _params.create(this); + + this.indexProvider = serverParams.indexProvider; + this.typeUtilProvider = serverParams.typeUtilProvider; + this.javaProjectFinder = serverParams.projectFinder; + this.projectObserver = serverParams.projectObserver; + this.completionFactory = new PropertyCompletionFactory(javaProjectFinder); this.yamlAssistContextProvider = new YamlAssistContextProvider() { @Override @@ -119,17 +114,7 @@ public class BootPropertiesLanguageServer extends SimpleLanguageServer { HoverInfoProvider hoverInfoProvider = getHoverProvider(); hoverEngine = new VscodeHoverEngineAdapter(this, hoverInfoProvider); - documents.onHover(hoverEngine::getHover); - - // Initialize project finders, project caches and project observers - MavenProjectCache mavenProjectCache = new MavenProjectCache(getWorkspaceService().getFileObserver(), MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); - javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); - - GradleProjectCache gradleProjectCache = new GradleProjectCache(getWorkspaceService().getFileObserver(), GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); - javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); - - projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache)); - + documents.onHover(hoverEngine::getHover); } private ICompletionEngine getCompletionEngine() { @@ -197,4 +182,8 @@ public class BootPropertiesLanguageServer extends SimpleLanguageServer { public ProjectObserver getProjectObserver() { return projectObserver; } + + public SpringPropertyIndexProvider getPropertiesIndexProvider() { + return indexProvider; + } } diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServerParams.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServerParams.java new file mode 100644 index 000000000..acfc4ba88 --- /dev/null +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/BootPropertiesLanguageServerParams.java @@ -0,0 +1,140 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot; + +import java.nio.file.Paths; +import java.util.Arrays; + +import org.eclipse.lsp4j.TextDocumentIdentifier; +import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; +import org.springframework.ide.vscode.boot.metadata.SpringPropertyIndexProvider; +import org.springframework.ide.vscode.boot.metadata.types.TypeUtil; +import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider; +import org.springframework.ide.vscode.commons.gradle.GradleCore; +import org.springframework.ide.vscode.commons.gradle.GradleProjectCache; +import org.springframework.ide.vscode.commons.gradle.GradleProjectFinder; +import org.springframework.ide.vscode.commons.java.BootProjectUtil; +import org.springframework.ide.vscode.commons.java.IJavaProject; +import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.CompositeProjectOvserver; +import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; +import org.springframework.ide.vscode.commons.languageserver.util.LSFactory; +import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; +import org.springframework.ide.vscode.commons.maven.MavenCore; +import org.springframework.ide.vscode.commons.maven.java.MavenProjectCache; +import org.springframework.ide.vscode.commons.maven.java.MavenProjectFinder; +import org.springframework.ide.vscode.commons.util.FileObserver; +import org.springframework.ide.vscode.commons.util.text.IDocument; + +/** + * Parameters for creating Boot Properties language server + * + * @author Alex Boyko + * + */ +public class BootPropertiesLanguageServerParams { + + public final JavaProjectFinder projectFinder; + public final ProjectObserver projectObserver; + public final SpringPropertyIndexProvider indexProvider; + public final TypeUtilProvider typeUtilProvider; + + public BootPropertiesLanguageServerParams( + JavaProjectFinder projectFinder, + ProjectObserver projectObserver, + SpringPropertyIndexProvider indexProvider, + TypeUtilProvider typeUtilProvider + ) { + super(); + this.projectFinder = projectFinder; + this.projectObserver = projectObserver; + this.indexProvider = indexProvider; + this.typeUtilProvider = typeUtilProvider; + } + + public static LSFactory createDefault() { + return (SimpleLanguageServer server) -> { + // Initialize project finders, project caches and project observers + FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); + CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); + MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); + + GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), true, Paths.get(IJavaProject.PROJECT_CACHE_FOLDER)); + javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); + + CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache)); + + DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver); + indexProvider.setProgressService(server.getProgressService()); + + return new BootPropertiesLanguageServerParams( + javaProjectFinder.filter(BootProjectUtil::isBootProject), + projectObserver, + indexProvider, + (IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))) + ); + }; + } + + public static LSFactory createTestDefault(SpringPropertyIndexProvider indexProvider, TypeUtilProvider typeUtilProvider) { + return (SimpleLanguageServer server) -> { + // Initialize project finders, project caches and project observers + FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); + CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); + MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null); + mavenProjectCache.setAlwaysFireEventOnFileChanged(true); + javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); + + GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null); + gradleProjectCache.setAlwaysFireEventOnFileChanged(true); + javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); + + CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache)); + + return new BootPropertiesLanguageServerParams( + javaProjectFinder.filter(BootProjectUtil::isBootProject), + projectObserver, + indexProvider, + typeUtilProvider + ); + }; + } + + public static LSFactory createTestDefault() { + return (SimpleLanguageServer server) -> { + // Initialize project finders, project caches and project observers + FileObserver fileObserver = server.getWorkspaceService().getFileObserver(); + CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); + MavenProjectCache mavenProjectCache = new MavenProjectCache(fileObserver, MavenCore.getDefault(), false, null); + mavenProjectCache.setAlwaysFireEventOnFileChanged(true); + javaProjectFinder.addJavaProjectFinder(new MavenProjectFinder(mavenProjectCache)); + + GradleProjectCache gradleProjectCache = new GradleProjectCache(fileObserver, GradleCore.getDefault(), false, null); + gradleProjectCache.setAlwaysFireEventOnFileChanged(true); + javaProjectFinder.addJavaProjectFinder(new GradleProjectFinder(gradleProjectCache)); + + CompositeProjectOvserver projectObserver = new CompositeProjectOvserver(Arrays.asList(mavenProjectCache, gradleProjectCache)); + + DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder, projectObserver); + indexProvider.setProgressService(server.getProgressService()); + + return new BootPropertiesLanguageServerParams( + javaProjectFinder.filter(BootProjectUtil::isBootProject), + projectObserver, + indexProvider, + (IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))) + ); + }; + } + +} diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/Main.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/Main.java index 14d056a1e..e24f911f9 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/Main.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/Main.java @@ -12,14 +12,7 @@ package org.springframework.ide.vscode.boot; import java.io.IOException; -import org.eclipse.lsp4j.TextDocumentIdentifier; -import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; -import org.springframework.ide.vscode.boot.metadata.types.TypeUtil; -import org.springframework.ide.vscode.boot.metadata.types.TypeUtilProvider; import org.springframework.ide.vscode.commons.languageserver.LaunguageServerApp; -import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder; -import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; -import org.springframework.ide.vscode.commons.util.text.IDocument; /** * Starts up Language Server process @@ -31,14 +24,8 @@ import org.springframework.ide.vscode.commons.util.text.IDocument; public class Main { public static void main(String[] args) throws IOException, InterruptedException { - LaunguageServerApp.start("boot-properties", () -> { - CompositeJavaProjectFinder javaProjectFinder = new CompositeJavaProjectFinder(); - DefaultSpringPropertyIndexProvider indexProvider = new DefaultSpringPropertyIndexProvider(javaProjectFinder); - TypeUtilProvider typeUtilProvider = (IDocument doc) -> new TypeUtil(javaProjectFinder.find(new TextDocumentIdentifier(doc.getUri()))); - SimpleLanguageServer server = new BootPropertiesLanguageServer(indexProvider, typeUtilProvider, javaProjectFinder); - indexProvider.setProgressService(server.getProgressService()); - return server; - }); + LaunguageServerApp.start("boot-properties", + () -> new BootPropertiesLanguageServer(BootPropertiesLanguageServerParams.createDefault())); } } diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java index c7a4571a4..2f1dea806 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/DefaultSpringPropertyIndexProvider.java @@ -17,6 +17,7 @@ import org.eclipse.lsp4j.TextDocumentIdentifier; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.ProgressService; import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.util.FuzzyMap; import org.springframework.ide.vscode.commons.util.text.IDocument; @@ -25,12 +26,13 @@ public class DefaultSpringPropertyIndexProvider implements SpringPropertyIndexPr private static final FuzzyMap EMPTY_INDEX = new SpringPropertyIndex(null, null); private JavaProjectFinder javaProjectFinder; - private SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager(ValueProviderRegistry.getDefault()); + private SpringPropertiesIndexManager indexManager; private ProgressService progressService = (id, msg) -> { /*ignore*/ }; - public DefaultSpringPropertyIndexProvider(JavaProjectFinder javaProjectFinder) { + public DefaultSpringPropertyIndexProvider(JavaProjectFinder javaProjectFinder, ProjectObserver projectObserver) { this.javaProjectFinder = javaProjectFinder; + this.indexManager = new SpringPropertiesIndexManager(ValueProviderRegistry.getDefault(), projectObserver); } @Override diff --git a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java index 7da6c2086..857ad7126 100644 --- a/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java +++ b/headless-services/boot-properties-language-server/src/main/java/org/springframework/ide/vscode/boot/metadata/SpringPropertiesIndexManager.java @@ -10,16 +10,18 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.metadata; -import java.util.HashMap; -import java.util.Map; +import java.util.concurrent.ExecutionException; import org.springframework.ide.vscode.boot.metadata.util.Listener; import org.springframework.ide.vscode.boot.metadata.util.ListenerManager; import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.languageserver.ProgressService; -import org.springframework.ide.vscode.commons.util.FuzzyMap; +import org.springframework.ide.vscode.commons.languageserver.java.ProjectObserver; import org.springframework.ide.vscode.commons.util.Log; +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; + /** * Support for Reconciling, Content Assist and Hover Text in spring properties * file all make use of a per-project index of spring properties metadata extracted @@ -30,41 +32,68 @@ import org.springframework.ide.vscode.commons.util.Log; */ public class SpringPropertiesIndexManager extends ListenerManager> { - private Map indexes = null; + private Cache indexes; private final ValueProviderRegistry valueProviders; private static int progressIdCt = 0; - public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders) { + public SpringPropertiesIndexManager(ValueProviderRegistry valueProviders, ProjectObserver projectObserver) { this.valueProviders = valueProviders; + this.indexes = CacheBuilder.newBuilder().build(); + if (projectObserver != null) { + projectObserver.addListener(new ProjectObserver.Listener() { + + @Override + public void created(IJavaProject project) { + // ignore + } + + @Override + public void changed(IJavaProject project) { + indexes.invalidate(project); + } + + @Override + public void deleted(IJavaProject project) { + indexes.invalidate(project); + } + + }); + } + } - public synchronized FuzzyMap get(IJavaProject project, ProgressService progressService) { - if (indexes==null) { - indexes = new HashMap<>(); + public synchronized SpringPropertyIndex get(IJavaProject project, ProgressService progressService) { + try { + return indexes.get(project, () -> initIndex(project, progressService)); + } catch (ExecutionException e) { + Log.log(e); + return null; } - SpringPropertyIndex index = indexes.get(project); - if (index==null) { - Log.info("Indexing Spring Boot Properties for "+project.getElementName()); - String progressId = getProgressId(); - if (progressService != null) { - progressService.progressEvent(progressId, "Indexing Spring Boot Properties..."); - } - - index = new SpringPropertyIndex(valueProviders, project.getClasspath()); - indexes.put(project, index); - - if (progressService != null) { - progressService.progressEvent(progressId, null); - } - Log.info("Indexing Spring Boot Properties for "+project.getElementName()+" DONE"); - Log.info("Indexed "+index.size()+" properties."); + } + + private SpringPropertyIndex initIndex(IJavaProject project, ProgressService progressService) { + Log.info("Indexing Spring Boot Properties for "+project.getElementName()); + + String progressId = getProgressId(); + if (progressService != null) { + progressService.progressEvent(progressId, "Indexing Spring Boot Properties..."); } + + SpringPropertyIndex index = new SpringPropertyIndex(valueProviders, project.getClasspath()); + + if (progressService != null) { + progressService.progressEvent(progressId, null); + } + + Log.info("Indexing Spring Boot Properties for "+project.getElementName()+" DONE"); + Log.info("Indexed "+index.size()+" properties."); + return index; } public synchronized void clear() { if (indexes!=null) { - indexes.clear(); + indexes.invalidateAll(); for (Listener l : getListeners()) { l.changed(this); } diff --git a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/metadata/PropertiesIndexTest.java b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/metadata/PropertiesIndexTest.java index f0df01135..9171b9e7d 100644 --- a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/metadata/PropertiesIndexTest.java +++ b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/metadata/PropertiesIndexTest.java @@ -36,7 +36,7 @@ public class PropertiesIndexTest { @Test public void springStandardPropertyPresent_Maven() throws Exception { SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager( - ValueProviderRegistry.getDefault()); + ValueProviderRegistry.getDefault(), null); IJavaProject mavenProject = projects.mavenProject(CUSTOM_PROPERTIES_PROJECT); FuzzyMap index = indexManager.get(mavenProject, progressService); PropertyInfo propertyInfo = index.get("server.port"); @@ -48,7 +48,7 @@ public class PropertiesIndexTest { @Test public void customPropertyPresent_Maven() throws Exception { SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager( - ValueProviderRegistry.getDefault()); + ValueProviderRegistry.getDefault(), null); IJavaProject mavenProject = projects.mavenProject(CUSTOM_PROPERTIES_PROJECT); FuzzyMap index = indexManager.get(mavenProject, progressService); PropertyInfo propertyInfo = index.get("demo.settings.user"); @@ -60,7 +60,7 @@ public class PropertiesIndexTest { @Test public void propertyNotPresent_Maven() throws Exception { SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager( - ValueProviderRegistry.getDefault()); + ValueProviderRegistry.getDefault(), null); IJavaProject mavenProject = projects.mavenProject(CUSTOM_PROPERTIES_PROJECT); FuzzyMap index = indexManager.get(mavenProject, progressService); PropertyInfo propertyInfo = index.get("my.server.port"); @@ -70,7 +70,7 @@ public class PropertiesIndexTest { @Test public void springStandardPropertyPresent_ClasspathFile() throws Exception { SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager( - ValueProviderRegistry.getDefault()); + ValueProviderRegistry.getDefault(), null); IJavaProject classpathFileProject = projects.javaProjectWithClasspathFile(CUSTOM_PROPERTIES_PROJECT); FuzzyMap index = indexManager.get(classpathFileProject, progressService); PropertyInfo propertyInfo = index.get("server.port"); @@ -82,7 +82,7 @@ public class PropertiesIndexTest { @Test public void customPropertyPresent_ClasspathFile() throws Exception { SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager( - ValueProviderRegistry.getDefault()); + ValueProviderRegistry.getDefault(), null); IJavaProject classpathFileProject = projects.javaProjectWithClasspathFile(CUSTOM_PROPERTIES_PROJECT); FuzzyMap index = indexManager.get(classpathFileProject, progressService); PropertyInfo propertyInfo = index.get("demo.settings.user"); @@ -94,7 +94,7 @@ public class PropertiesIndexTest { @Test public void propertyNotPresent_ClasspathFile() throws Exception { SpringPropertiesIndexManager indexManager = new SpringPropertiesIndexManager( - ValueProviderRegistry.getDefault()); + ValueProviderRegistry.getDefault(), null); IJavaProject classpathFileProject = projects.javaProjectWithClasspathFile(CUSTOM_PROPERTIES_PROJECT); FuzzyMap index = indexManager.get(classpathFileProject, progressService); PropertyInfo propertyInfo = index.get("my.server.port"); diff --git a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java index 7b16749b0..3fc4f10fc 100644 --- a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java +++ b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationPropertiesEditorTest.java @@ -25,6 +25,7 @@ import org.eclipse.lsp4j.Diagnostic; import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.boot.BootPropertiesLanguageServer; +import org.springframework.ide.vscode.boot.BootPropertiesLanguageServerParams; import org.springframework.ide.vscode.boot.editor.harness.AbstractPropsEditorTest; import org.springframework.ide.vscode.boot.editor.harness.StyledStringMatcher; import org.springframework.ide.vscode.boot.metadata.CachingValueProvider; @@ -1585,7 +1586,9 @@ public class ApplicationPropertiesEditorTest extends AbstractPropsEditorTest { @Override protected SimpleLanguageServer newLanguageServer() { - BootPropertiesLanguageServer server = new BootPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder); + BootPropertiesLanguageServer server = new BootPropertiesLanguageServer( + s -> new BootPropertiesLanguageServerParams(javaProjectFinder, null, md.getIndexProvider(), + typeUtilProvider)); server.setMaxCompletionsNumber(-1); return server; } diff --git a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java index c8f431a57..c220c928e 100644 --- a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java +++ b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/ApplicationYamlEditorTest.java @@ -21,6 +21,7 @@ import org.eclipse.lsp4j.Diagnostic; import org.junit.Ignore; import org.junit.Test; import org.springframework.ide.vscode.boot.BootPropertiesLanguageServer; +import org.springframework.ide.vscode.boot.BootPropertiesLanguageServerParams; import org.springframework.ide.vscode.boot.editor.harness.AbstractPropsEditorTest; import org.springframework.ide.vscode.boot.editor.harness.StyledStringMatcher; import org.springframework.ide.vscode.boot.metadata.CachingValueProvider; @@ -3670,7 +3671,9 @@ public class ApplicationYamlEditorTest extends AbstractPropsEditorTest { @Override protected SimpleLanguageServer newLanguageServer() { - BootPropertiesLanguageServer server = new BootPropertiesLanguageServer(md.getIndexProvider(), typeUtilProvider, javaProjectFinder); + BootPropertiesLanguageServer server = new BootPropertiesLanguageServer( + s -> new BootPropertiesLanguageServerParams(javaProjectFinder, null, md.getIndexProvider(), + typeUtilProvider)); server.setMaxCompletionsNumber(-1); return server; } diff --git a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/BootPropertiesLanguageServerTest.java b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/BootPropertiesLanguageServerTest.java index d9a598010..624205caf 100644 --- a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/BootPropertiesLanguageServerTest.java +++ b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/BootPropertiesLanguageServerTest.java @@ -21,7 +21,7 @@ import org.eclipse.lsp4j.InitializeResult; import org.eclipse.lsp4j.TextDocumentSyncKind; import org.junit.Test; import org.springframework.ide.vscode.boot.BootPropertiesLanguageServer; -import org.springframework.ide.vscode.commons.languageserver.java.CompositeJavaProjectFinder; +import org.springframework.ide.vscode.boot.BootPropertiesLanguageServerParams; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; @@ -38,7 +38,7 @@ public class BootPropertiesLanguageServerTest { } private LanguageServerHarness newHarness() throws Exception { - Callable f = () -> new BootPropertiesLanguageServer((d) -> null, (d) -> null, new CompositeJavaProjectFinder()); + Callable f = () -> new BootPropertiesLanguageServer(BootPropertiesLanguageServerParams.createTestDefault()); return new LanguageServerHarness(f); } diff --git a/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/SpringPropertiesIndexTest.java b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/SpringPropertiesIndexTest.java new file mode 100644 index 000000000..28198f3ac --- /dev/null +++ b/headless-services/boot-properties-language-server/src/test/java/org/springframework/ide/vscode/boot/test/SpringPropertiesIndexTest.java @@ -0,0 +1,83 @@ +/******************************************************************************* + * Copyright (c) 2017 Pivotal, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Pivotal, Inc. - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.boot.test; + +import static org.mockito.Matchers.anyObject; +import static org.mockito.Mockito.atLeastOnce; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +import java.io.File; + +import org.junit.Before; +import org.junit.Test; +import org.springframework.ide.vscode.boot.BootPropertiesLanguageServer; +import org.springframework.ide.vscode.boot.BootPropertiesLanguageServerParams; +import org.springframework.ide.vscode.boot.metadata.DefaultSpringPropertyIndexProvider; +import org.springframework.ide.vscode.commons.languageserver.ProgressService; +import org.springframework.ide.vscode.commons.maven.MavenCore; +import org.springframework.ide.vscode.commons.util.text.LanguageId; +import org.springframework.ide.vscode.commons.util.text.TextDocument; +import org.springframework.ide.vscode.languageserver.testharness.LanguageServerHarness; +import org.springframework.ide.vscode.project.harness.ProjectsHarness; + +/** + * Tests for Boot properties index + * + * @author Alex Boyko + * + */ +public class SpringPropertiesIndexTest { + + private LanguageServerHarness harness; + + private DefaultSpringPropertyIndexProvider propertyIndexProvider; + + @Before + public void setup() throws Exception { + harness = new LanguageServerHarness<>(() -> new BootPropertiesLanguageServer(BootPropertiesLanguageServerParams.createTestDefault())); + } + + @Test + public void testPropertiesIndexRefreshOnProjectChange() throws Exception { + harness.intialize(new File(ProjectsHarness.class.getResource("/test-projects/boot-1.2.0-properties-live-metadta/").toURI())); + propertyIndexProvider = (DefaultSpringPropertyIndexProvider) harness.getServer().getPropertiesIndexProvider(); + + File directory = new File(ProjectsHarness.class.getResource("/test-projects/boot-1.2.0-properties-live-metadta/").toURI()); + + File javaFile = new File(directory, "/src/main/java/demo/Application.java"); + + TextDocument doc = new TextDocument(javaFile.toURI().toString(), LanguageId.JAVA); + + // Not cached yet, hence progress service invoked + ProgressService progressService = mock(ProgressService.class); + propertyIndexProvider.setProgressService(progressService); + propertyIndexProvider.getIndex(doc); + verify(progressService, atLeastOnce()).progressEvent(anyObject(), anyObject()); + + // Should be cached now, so progress service should not be touched + progressService = mock(ProgressService.class); + propertyIndexProvider.setProgressService(progressService); + propertyIndexProvider.getIndex(doc); + verify(progressService, never()).progressEvent(anyObject(), anyObject()); + + // Change POM file for the project + harness.changeFile(new File(directory, MavenCore.POM_XML).toURI().toString()); + + // POM has changed, hence project needs to be reloaded, cached value is cleared + progressService = mock(ProgressService.class); + propertyIndexProvider.setProgressService(progressService); + propertyIndexProvider.getIndex(doc); + verify(progressService, atLeastOnce()).progressEvent(anyObject(), anyObject()); + } + +} diff --git a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/BootProjectUtil.java b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/BootProjectUtil.java similarity index 85% rename from headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/BootProjectUtil.java rename to headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/BootProjectUtil.java index dd3272316..f9545e954 100644 --- a/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/BootProjectUtil.java +++ b/headless-services/commons/commons-java/src/main/java/org/springframework/ide/vscode/commons/java/BootProjectUtil.java @@ -8,12 +8,10 @@ * Contributors: * Pivotal, Inc. - initial API and implementation *******************************************************************************/ -package org.springframework.ide.vscode.boot.java.utils; +package org.springframework.ide.vscode.commons.java; import java.nio.file.Path; -import org.springframework.ide.vscode.commons.java.IClasspath; -import org.springframework.ide.vscode.commons.java.IJavaProject; import org.springframework.ide.vscode.commons.util.Log; public class BootProjectUtil { diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java index a8711f3b7..759d77d70 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/LaunguageServerApp.java @@ -177,7 +177,7 @@ public abstract class LaunguageServerApp { * @return */ protected ExecutorService createServerThreads() { - return Executors.newSingleThreadExecutor(); + return Executors.newCachedThreadPool(); } private Launcher createSocketLauncher(Object localService, Class remoteInterface, SocketAddress socketAddress, ExecutorService executorService, Function wrapper) throws IOException { diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java index c8f868ae2..969a32a01 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/STS4LanguageClient.java @@ -23,7 +23,7 @@ import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEd * * @author Kris De Volder */ -public interface STS4LanguageClient extends LanguageClient { +public interface STS4LanguageClient extends LanguageClient, WorkspaceFoldersProposedClient { @JsonNotification("sts/highlight") void highlight(HighlightParams highlights); diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/WorkspaceFoldersProposedClient.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/WorkspaceFoldersProposedClient.java new file mode 100644 index 000000000..d5127e39e --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/WorkspaceFoldersProposedClient.java @@ -0,0 +1,31 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.languageserver; + +import java.util.concurrent.CompletableFuture; + +import org.eclipse.lsp4j.jsonrpc.services.JsonRequest; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; + +public interface WorkspaceFoldersProposedClient { + + /** + * The workspace/workspaceFolders request is sent from the server to the client + * to fetch the current open list of workspace folders. + * + * @return Returns the current open list of workspace folders. Returns null in + * the response if only a single file is open in the tool. Returns an + * empty array if a workspace is open but no folders are configured. + */ + @JsonRequest("workspace/workspaceFolders") + CompletableFuture getWorkspaceFolders(); + +} diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/DidChangeWorkspaceFoldersParams.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/DidChangeWorkspaceFoldersParams.java new file mode 100644 index 000000000..49cb1f521 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/DidChangeWorkspaceFoldersParams.java @@ -0,0 +1,93 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.languageserver.multiroot; + +import org.eclipse.lsp4j.jsonrpc.validation.NonNull; + +public class DidChangeWorkspaceFoldersParams { + /** + * The actual workspace folder change event. + */ + @NonNull + private WorkspaceFoldersChangeEvent event; + + public DidChangeWorkspaceFoldersParams() { + // TODO Auto-generated constructor stub + } + + /** + * @param event + */ + public DidChangeWorkspaceFoldersParams(@NonNull WorkspaceFoldersChangeEvent event) { + this.event = event; + } + + /** + * @return the event + */ + @NonNull + public WorkspaceFoldersChangeEvent getEvent() { + return event; + } + + /** + * @param event + * the event to set + */ + public void setEvent(@NonNull WorkspaceFoldersChangeEvent event) { + this.event = event; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "DidChangeWorkspaceFoldersParams [event=" + event + "]"; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((event == null) ? 0 : event.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + DidChangeWorkspaceFoldersParams other = (DidChangeWorkspaceFoldersParams) obj; + if (event == null) { + if (other.event != null) { + return false; + } + } else if (!event.equals(other.event)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/README.txt b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/README.txt new file mode 100644 index 000000000..44172b93a --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/README.txt @@ -0,0 +1,4 @@ +Source code in this folder are copied from org.eclipse.jdt.ls.core.internal.lsp + +They provide support for the proposed LSP protocol extension to deal with multi-root +workspaces. \ No newline at end of file diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFolder.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFolder.java new file mode 100644 index 000000000..9bc15d72f --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFolder.java @@ -0,0 +1,128 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.languageserver.multiroot; + +import org.eclipse.lsp4j.jsonrpc.validation.NonNull; + +public class WorkspaceFolder { + /** + * The associated URI for this workspace folder. + */ + @NonNull + private String uri; + + /** + * The name of the workspace folder. Defaults to the uri's basename. + */ + @NonNull + private String name; + + public WorkspaceFolder() { + } + + public WorkspaceFolder(@NonNull String uri, @NonNull String name) { + this.uri = uri; + this.name = name; + } + + /** + * Gets the associated URI for this workspace folder. + * + * @return the uri + */ + @NonNull + public String getUri() { + return uri; + } + + /** + * Sets the associated URI for this workspace folder. + * + * @param uri + * the uri to set + */ + public void setUri(@NonNull String uri) { + this.uri = uri; + } + + /** + * The name of the workspace folder. Defaults to the uri's basename. + * + * @return the name + */ + @NonNull + public String getName() { + return name; + } + + /** + * The name of the workspace folder. Defaults to the uri's basename. + * + * @param name + * the name to set + */ + public void setName(@NonNull String name) { + this.name = name; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "WorkspaceFolder [uri=" + uri + ", name=" + name + "]"; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((name == null) ? 0 : name.hashCode()); + result = prime * result + ((uri == null) ? 0 : uri.hashCode()); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + WorkspaceFolder other = (WorkspaceFolder) obj; + if (name == null) { + if (other.name != null) { + return false; + } + } else if (!name.equals(other.name)) { + return false; + } + if (uri == null) { + if (other.uri != null) { + return false; + } + } else if (!uri.equals(other.uri)) { + return false; + } + return true; + } + +} \ No newline at end of file diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFoldersChangeEvent.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFoldersChangeEvent.java new file mode 100644 index 000000000..6f96fb2d8 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFoldersChangeEvent.java @@ -0,0 +1,117 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.languageserver.multiroot; + +import java.util.Arrays; + +import org.eclipse.lsp4j.jsonrpc.validation.NonNull; + +/** + * The workspace folder change event. + */ +public class WorkspaceFoldersChangeEvent { + /** + * The array of added workspace folders + */ + @NonNull + private WorkspaceFolder[] added; + + /** + * The array of the removed workspace folders + */ + @NonNull + private WorkspaceFolder[] removed; + + public WorkspaceFoldersChangeEvent() { + } + + public WorkspaceFoldersChangeEvent(@NonNull final WorkspaceFolder[] added, @NonNull final WorkspaceFolder[] removed) { + this.added = added; + this.removed = removed; + } + + /** + * @return the added + */ + @NonNull + public WorkspaceFolder[] getAdded() { + return added; + } + + /** + * @param added + * the added to set + */ + public void setAdded(@NonNull WorkspaceFolder[] added) { + this.added = added; + } + + /** + * @return the removed + */ + @NonNull + public WorkspaceFolder[] getRemoved() { + return removed; + } + + /** + * @param removed + * the removed to set + */ + public void setRemoved(@NonNull WorkspaceFolder[] removed) { + this.removed = removed; + } + + /* (non-Javadoc) + * @see java.lang.Object#hashCode() + */ + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + Arrays.hashCode(added); + result = prime * result + Arrays.hashCode(removed); + return result; + } + + /* (non-Javadoc) + * @see java.lang.Object#equals(java.lang.Object) + */ + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj == null) { + return false; + } + if (getClass() != obj.getClass()) { + return false; + } + WorkspaceFoldersChangeEvent other = (WorkspaceFoldersChangeEvent) obj; + if (!Arrays.equals(added, other.added)) { + return false; + } + if (!Arrays.equals(removed, other.removed)) { + return false; + } + return true; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return "WorkspaceFoldersChangeEvent [added=" + Arrays.toString(added) + ", removed=" + Arrays.toString(removed) + "]"; + } + +} \ No newline at end of file diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFoldersProposedService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFoldersProposedService.java new file mode 100644 index 000000000..0ce838650 --- /dev/null +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/multiroot/WorkspaceFoldersProposedService.java @@ -0,0 +1,43 @@ +/******************************************************************************* + * Copyright (c) 2017 Microsoft Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Microsoft Corporation - initial API and implementation + *******************************************************************************/ +package org.springframework.ide.vscode.commons.languageserver.multiroot; + +import java.util.UUID; + +import org.eclipse.lsp4j.jsonrpc.services.JsonNotification; +import org.eclipse.lsp4j.jsonrpc.services.JsonSegment; + +/** + * Server APIs for Workspace folders (prop + * https://github.com/Microsoft/vscode-languageserver-node/blob/master/protocol/src/protocol.workspaceFolders.proposed.md + * + */ +@JsonSegment("workspace") +public interface WorkspaceFoldersProposedService { + + public static final String CAPABILITY_NAME = "workspace/didChangeWorkspaceFolders"; + public static final String CAPABILITY_ID = UUID.randomUUID().toString(); + + /** + * The workspace/didChangeWorkspaceFolders notification is sent from the client + * to the server to inform the client about workspace folder configuration + * changes. + * + * @param documentUri + * the document from which the project configuration will be updated + */ + @JsonNotification + void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params); + + + +} + diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java index 328134aff..6c097efd9 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -13,8 +13,10 @@ package org.springframework.ide.vscode.commons.languageserver.util; import java.nio.file.Path; import java.nio.file.Paths; import java.util.ArrayList; +import java.util.Collection; import java.util.LinkedHashSet; import java.util.List; +import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.CompletableFuture; @@ -43,6 +45,9 @@ import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionEngine; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter; import org.springframework.ide.vscode.commons.languageserver.completion.VscodeCompletionEngineAdapter.LazyCompletionResolver; +import org.springframework.ide.vscode.commons.languageserver.multiroot.DidChangeWorkspaceFoldersParams; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFoldersProposedService; import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix; import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit; @@ -70,7 +75,7 @@ import reactor.core.scheduler.Schedulers; * here so we can try to keep the subclass itself more 'clutter free' and focus on * what its really doing and not the 'wiring and plumbing'. */ -public abstract class SimpleLanguageServer implements LanguageServer, LanguageClientAware, ServiceNotificationsClient { +public abstract class SimpleLanguageServer implements LanguageServer, LanguageClientAware, ServiceNotificationsClient, WorkspaceFoldersProposedService { private static final Scheduler RECONCILER_SCHEDULER = Schedulers.newSingle("Reconciler"); @@ -78,8 +83,6 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl private final String CODE_ACTION_COMMAND_ID; protected final LazyCompletionResolver completionResolver = createCompletionResolver(); - private Path workspaceRoot; - private SimpleTextDocumentService tds; private SimpleWorkspaceService workspace; @@ -169,16 +172,28 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl @Override public CompletableFuture initialize(InitializeParams params) { Log.debug("Initializing: "+params); - String rootPath = params.getRootPath(); - if (rootPath==null) { - Log.debug("workspaceRoot NOT SET"); - } else { - this.workspaceRoot= Paths.get(rootPath).toAbsolutePath().normalize(); + + // multi-root workspace handling + List workspaceFolders = getWorkspaceFolders(params); + if (!workspaceFolders.isEmpty()) { + this.getWorkspaceService().setWorkspaceFolders(workspaceFolders); } + else { + String rootPath = params.getRootPath(); + if (rootPath==null) { + Log.debug("workspaceRoot NOT SET"); + } else { + List singleRootFolder = new ArrayList<>(); + Path path = Paths.get(rootPath); + singleRootFolder.add(new WorkspaceFolder(path.toUri().toString(), path.getFileName().toString())); + this.getWorkspaceService().setWorkspaceFolders(singleRootFolder); + } + } + this.hasCompletionSnippetSupport = safeGet(false, () -> params.getCapabilities().getTextDocument().getCompletion().getCompletionItem().getSnippetSupport()); this.hasExecuteCommandSupport = safeGet(false, () -> params.getCapabilities().getWorkspace().getExecuteCommand()!=null); this.hasFileWatcherRegistrationSupport = safeGet(false, () -> params.getCapabilities().getWorkspace().getDidChangeWatchedFiles().getDynamicRegistration()); - Log.debug("workspaceRoot = "+workspaceRoot); + Log.debug("workspaceRoots = "+getWorkspaceService().getWorkspaceRoots()); Log.debug("hasCompletionSnippetSupport = "+hasCompletionSnippetSupport); Log.debug("hasExecuteCommandSupport = "+hasExecuteCommandSupport); @@ -193,6 +208,31 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl return CompletableFuture.completedFuture(result); } + private List getWorkspaceFolders(InitializeParams params) { + List initialFolders = new ArrayList<>(); + + Object initOptions = params.getInitializationOptions(); + if (initOptions != null && initOptions instanceof Map) { + Map initializationOptions = (Map) initOptions; + Object folders = initializationOptions.get("workspaceFolders"); + if (folders != null && folders instanceof List) { + List workspaceFolders = (List) folders; + for (Object object : workspaceFolders) { + String folderPath = object.toString(); + String folderName = null; + + int folderNameStart = folderPath.lastIndexOf("/"); + if (folderNameStart > 0) { + folderName = folderPath.substring(folderPath.lastIndexOf("/") + 1); + } + initialFolders.add(new WorkspaceFolder(object.toString(), folderName)); + } + } + } + + return initialFolders; + } + /** * Get some info safely. If there's any kind of exception, ignore it * and retutn default value instead. @@ -307,10 +347,29 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl System.exit(0); } - public Path getWorkspaceRoot() { - return workspaceRoot; + public Collection getWorkspaceRoots() { + return getWorkspaceService().getWorkspaceRoots(); } +// /** +// * Deprecated, shouldn't use and should be removed. Anyone calling this +// * will have problems handling multi-root workspaces. +// *

+// * Use getWorkspaceRoots instead. +// */ +// @Deprecated +// public Path getWorkspaceRoot() { +// try { +// Optional firstRoot = getWorkspaceRoots().stream().findFirst(); +// if (firstRoot.isPresent()) { +// return new File(new URI(firstRoot.get().getUri())).toPath(); +// } +// } catch (Exception e) { +// Log.log(e); +// } +// return null; +// } + @Override public synchronized SimpleTextDocumentService getTextDocumentService() { if (tds==null) { @@ -467,4 +526,10 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl public boolean canRegisterFileWatchersDynamically() { return hasFileWatcherRegistrationSupport; } + + @Override + public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) { + getWorkspaceService().didChangeWorkspaceFolders(params); + } + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java index 1a8097e8f..2463db6e1 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java @@ -10,7 +10,10 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.util; +import java.util.Collection; +import java.util.HashSet; import java.util.List; +import java.util.Set; import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; @@ -20,6 +23,10 @@ import org.eclipse.lsp4j.ExecuteCommandParams; import org.eclipse.lsp4j.SymbolInformation; import org.eclipse.lsp4j.WorkspaceSymbolParams; import org.eclipse.lsp4j.services.WorkspaceService; +import org.springframework.ide.vscode.commons.languageserver.multiroot.DidChangeWorkspaceFoldersParams; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFoldersChangeEvent; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFoldersProposedService; import org.springframework.ide.vscode.commons.util.Assert; import org.springframework.ide.vscode.commons.util.FileObserver; import org.springframework.ide.vscode.commons.util.Log; @@ -28,15 +35,18 @@ import com.google.common.collect.ImmutableList; import reactor.core.publisher.Mono; -public class SimpleWorkspaceService implements WorkspaceService { +public class SimpleWorkspaceService implements WorkspaceService, WorkspaceFoldersProposedService { private SimpleLanguageServer server; + private Set workspaceRoots = new HashSet<>(); private ListenerList configurationListeners = new ListenerList<>(); private ExecuteCommandHandler executeCommandHandler; private WorkspaceSymbolHandler workspaceSymbolHandler; private SimpleServerFileObserver fileObserver; + private ListenerList workspaceFolderListeners = new ListenerList<>(); + public SimpleWorkspaceService(SimpleLanguageServer server) { this.server = server; this.fileObserver = new SimpleServerFileObserver(server); @@ -89,6 +99,27 @@ public class SimpleWorkspaceService implements WorkspaceService { }); } + @Override + public synchronized void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) { + WorkspaceFoldersChangeEvent evt = params.getEvent(); + boolean changed = false; + for (WorkspaceFolder r : evt.getAdded()) { + workspaceRoots.add(r); + changed = true; + } + for (WorkspaceFolder r : evt.getRemoved()) { + workspaceRoots.remove(r); + changed = true; + } + if (changed) { + workspaceFolderListeners.fire(params); + } + } + + public void onDidChangeWorkspaceFolders(Consumer l) { + workspaceFolderListeners.add(l); + } + @Override public CompletableFuture executeCommand(ExecuteCommandParams params) { if (this.executeCommandHandler!=null) { @@ -123,4 +154,14 @@ public class SimpleWorkspaceService implements WorkspaceService { fileObserver.dispose(); } + public Collection getWorkspaceRoots() { + return ImmutableList.copyOf(workspaceRoots); + } + + public synchronized void setWorkspaceFolders(List workspaceFolders) { + workspaceRoots = new HashSet<>(); + workspaceRoots.addAll(workspaceFolders); + } + + } diff --git a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java index e2ace4ecc..5c2c868f9 100644 --- a/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java +++ b/headless-services/commons/commons-maven/src/main/java/org/springframework/ide/vscode/commons/maven/java/MavenJavaProject.java @@ -48,4 +48,9 @@ public class MavenJavaProject extends AbstractJavaProject { return classpath.update(); } + @Override + public String toString() { + return "MavenJavaProject("+classpath.getName()+")"; + } + } diff --git a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java index 0d13fb6be..afe6b9f07 100644 --- a/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java +++ b/headless-services/commons/language-server-test-harness/src/main/java/org/springframework/ide/vscode/languageserver/testharness/LanguageServerHarness.java @@ -94,6 +94,7 @@ import org.springframework.ide.vscode.commons.languageserver.HighlightParams; import org.springframework.ide.vscode.commons.languageserver.ProgressParams; import org.springframework.ide.vscode.commons.languageserver.STS4LanguageClient; import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits; +import org.springframework.ide.vscode.commons.languageserver.multiroot.WorkspaceFolder; import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixEdit.CursorMovement; import org.springframework.ide.vscode.commons.languageserver.util.LanguageServerTestListener; import org.springframework.ide.vscode.commons.languageserver.util.Settings; @@ -286,6 +287,16 @@ public class LanguageServerHarness { } return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false)); } + + @Override + public CompletableFuture getWorkspaceFolders() { + if (workspaceRoot!=null) { + return CompletableFuture.completedFuture(new WorkspaceFolder[]{ + new WorkspaceFolder(workspaceRoot.toURI().toString(), workspaceRoot.getName()) + }); + } + return CompletableFuture.completedFuture(new WorkspaceFolder[]{}); + } }); } diff --git a/vscode-extensions/commons-vscode/package.json b/vscode-extensions/commons-vscode/package.json index ce13187be..fec38eee4 100644 --- a/vscode-extensions/commons-vscode/package.json +++ b/vscode-extensions/commons-vscode/package.json @@ -30,6 +30,6 @@ "typescript": "^2.3.0", "@types/node": "^6.0.68", "vscode": "^1.1.5", - "vscode-languageclient": "^3.4.2" + "vscode-languageclient": "^3.4.5" } } diff --git a/vscode-extensions/commons-vscode/src/launch-util.ts b/vscode-extensions/commons-vscode/src/launch-util.ts index 39857c70c..6c04b35fc 100644 --- a/vscode-extensions/commons-vscode/src/launch-util.ts +++ b/vscode-extensions/commons-vscode/src/launch-util.ts @@ -16,6 +16,7 @@ import { Trace, NotificationType } from 'vscode-jsonrpc'; import * as P2C from 'vscode-languageclient/lib/protocolConverter'; import {WorkspaceEdit, Position} from 'vscode-languageserver-types'; import {HighlightService, HighlightParams} from './highlight-service'; +import { log } from 'util'; let p2c = P2C.createConverter(); @@ -83,7 +84,8 @@ export function activate(options: ActivatorOptions, context: VSCode.ExtensionCon reader: socket, writer: socket }); - }).listen(port, () => { + }) + .listen(port, () => { let processLaunchoptions = { cwd: VSCode.workspace.rootPath }; @@ -151,6 +153,8 @@ function setupLanguageClient(context: VSCode.ExtensionContext, createServer: Ser let client = new LanguageClient(options.extensionId, options.extensionId, createServer, options.clientOptions ); + client.registerProposedFeatures(); + log("Proposed protocol extensions loaded!"); if (options.TRACE) { client.trace = Trace.Verbose; } diff --git a/vscode-extensions/vscode-boot-java/lib/Main.ts b/vscode-extensions/vscode-boot-java/lib/Main.ts index 78f0d4c5d..d94520247 100644 --- a/vscode-extensions/vscode-boot-java/lib/Main.ts +++ b/vscode-extensions/vscode-boot-java/lib/Main.ts @@ -10,7 +10,7 @@ import * as FS from 'fs'; import * as Net from 'net'; import * as ChildProcess from 'child_process'; import { LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo } from 'vscode-languageclient'; -import { TextDocument } from 'vscode'; +import { workspace, TextDocument } from 'vscode'; import { Trace } from 'vscode-jsonrpc'; import * as commons from 'commons-vscode'; @@ -38,7 +38,10 @@ export function activate(context: VSCode.ExtensionContext) { documentSelector: ['java'], synchronize: { configurationSection: 'boot-java' - } + }, + initializationOptions: { + workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null + }, } }; commons.activate(options, context); diff --git a/vscode-extensions/vscode-boot-java/package.json b/vscode-extensions/vscode-boot-java/package.json index 4bb7d2cee..a50ce5f27 100644 --- a/vscode-extensions/vscode-boot-java/package.json +++ b/vscode-extensions/vscode-boot-java/package.json @@ -48,7 +48,7 @@ "vsce-package": "vsce package" }, "dependencies": { - "vscode-languageclient": "^3.4.2", + "vscode-languageclient": "^3.4.5", "commons-vscode": "^0.0.6" }, "devDependencies": {