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 ea021fa6c..c6a49313e 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,8 +10,12 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java; +import java.io.File; +import java.net.URI; +import java.nio.file.Path; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; import org.eclipse.lsp4j.CompletionItemKind; @@ -55,12 +59,14 @@ 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.WorkspaceFolder; 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; import org.springframework.ide.vscode.commons.languageserver.util.SimpleLanguageServer; import org.springframework.ide.vscode.commons.languageserver.util.SimpleTextDocumentService; import org.springframework.ide.vscode.commons.languageserver.util.SimpleWorkspaceService; +import org.springframework.ide.vscode.commons.util.Log; import org.springframework.ide.vscode.commons.util.text.TextDocument; import com.google.common.collect.ImmutableList; @@ -173,7 +179,7 @@ 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; } @@ -360,4 +366,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/utils/SpringIndexer.java b/headless-services/boot-java-language-server/src/main/java/org/springframework/ide/vscode/boot/java/utils/SpringIndexer.java index 2c423a083..87314a324 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 @@ -53,6 +53,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.text.LanguageId; import org.springframework.ide.vscode.commons.util.text.TextDocument; @@ -102,14 +103,14 @@ public class SpringIndexer { this.symbols = Collections.synchronizedList(new ArrayList<>()); this.symbolsByDoc = new ConcurrentHashMap<>(); - } - public CompletableFuture initialize(final Path workspaceRoot) { - if (workspaceRoot==null) { - return CompletableFuture.completedFuture(null); - } + public CompletableFuture initialize(Collection workspaceRoots) { synchronized(this) { + server.getWorkspaceService().onDidChangeWorkspaceFolders(evt -> { + System.err.println("Workspace roots have changed!"); + refresh(); + }); if (this.initializeTask == null) { initializing.set(true); if (server.getProjectObserver() != null) { @@ -118,7 +119,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() { @@ -139,13 +142,12 @@ public class SpringIndexer { } } }, "Spring Annotation Index Update Worker"); - updateWorker.start(); initializing.set(false); } + }); } - return this.initializeTask; } } @@ -161,7 +163,7 @@ public class SpringIndexer { symbols.clear(); symbolsByDoc.clear(); Log.info("Rebuilding SpringIndexer..."); - initialize(server.getWorkspaceRoot()); + initialize(server.getWorkspaceRoots()); } } @@ -264,9 +266,9 @@ public class SpringIndexer { return queryindex == queryChars.length; } - 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()) @@ -280,6 +282,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 93e1b3cc8..8ce8b50e7 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; @@ -28,9 +29,12 @@ import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; 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 */ @@ -55,7 +59,7 @@ public class SpringIndexerBeansTest { public void testScanSimpleConfigurationClass() throws Exception { SpringIndexer indexer = new SpringIndexer(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); List symbols = indexer.getSymbols(uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java"); @@ -63,11 +67,21 @@ public class SpringIndexerBeansTest { assertTrue(containsSymbol(symbols, "@+ 'simpleBean' (@Bean) BeanClass", uriPrefix + "/src/main/java/org/test/SimpleConfiguration.java", 8, 1, 8, 8)); } + private Collection wsFolder(File directory) { + if (directory!=null) { + return ImmutableList.of(new WorkspaceFolder( + directory.toURI().toString(), + directory.getName() + )); + } + return ImmutableList.of(); + } + @Test public void testScanSimpleFunctionBean() throws Exception { SpringIndexer indexer = new SpringIndexer(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); List symbols = indexer.getSymbols(uriPrefix + "/src/main/java/org/test/FunctionClass.java"); @@ -79,7 +93,7 @@ public class SpringIndexerBeansTest { public void testScanSimpleComponentClass() throws Exception { SpringIndexer indexer = new SpringIndexer(harness.getServer(), projectFinder, symbolProviders); File directory = new File(ProjectsHarness.class.getResource("/test-projects/test-annotation-indexing-beans/").toURI()); - indexer.initialize(directory.toPath()); + indexer.initialize(wsFolder(directory)); String uriPrefix = "file://" + directory.getAbsolutePath(); List symbols = indexer.getSymbols(uriPrefix + "/src/main/java/org/test/SimpleComponent.java"); 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/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..4d9f16a7f 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 @@ -10,9 +10,9 @@ *******************************************************************************/ 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.concurrent.Callable; @@ -43,6 +43,7 @@ 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.WorkspaceFolder; 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; @@ -78,8 +79,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; @@ -173,12 +172,12 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl if (rootPath==null) { Log.debug("workspaceRoot NOT SET"); } else { - this.workspaceRoot= Paths.get(rootPath).toAbsolutePath().normalize(); + this.getWorkspaceService().setRoot(Paths.get(rootPath)); } 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); @@ -307,10 +306,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) { 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..3e6003914 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,11 @@ *******************************************************************************/ package org.springframework.ide.vscode.commons.languageserver.util; +import java.nio.file.Path; +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 +24,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 +36,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 +100,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 +155,14 @@ public class SimpleWorkspaceService implements WorkspaceService { fileObserver.dispose(); } + public Collection getWorkspaceRoots() { + return ImmutableList.copyOf(workspaceRoots); + } + + public synchronized void setRoot(Path path) { + workspaceRoots = new HashSet<>(); + workspaceRoots.add(new WorkspaceFolder(path.toUri().toString(), path.getFileName().toString())); + } + + } 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 8e0728bb4..dd4cd753b 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 @@ -37,5 +37,10 @@ public class MavenJavaProject implements IJavaProject { void update(MavenCore maven) { this.classpath = new MavenProjectClasspath(maven, classpath.getPomFile()); } + + @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/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; }