Merge branch 'multi-root'

This commit is contained in:
Martin Lippert
2017-11-17 20:05:10 +01:00
22 changed files with 656 additions and 62 deletions

View File

@@ -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<InitializeResult> initialize(InitializeParams params) {
CompletableFuture<InitializeResult> 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;
}
}

View File

@@ -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<Void> initialize(final Path workspaceRoot) {
if (workspaceRoot==null) {
return CompletableFuture.completedFuture(null);
}
public CompletableFuture<Void> initialize(Collection<WorkspaceFolder> 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<Optional<IJavaProject>, List<String>> projects = Files.walk(directory.toPath())
Map<Optional<IJavaProject>, List<String>> 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);

View File

@@ -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<List<? extends Location>> findReferencesFromPropertyFiles(Path workspaceRoot,
String propertyKey) {
public CompletableFuture<List<? extends Location>> findReferencesFromPropertyFiles(
Collection<WorkspaceFolder> workspaceRoots,
String propertyKey
) {
for (WorkspaceFolder workspaceFolder : workspaceRoots) {
try {
Path workspaceRoot = Paths.get(new URI(workspaceFolder.getUri()));
try (Stream<Path> walk = Files.walk(workspaceRoot)) {
List<Location> locations = walk
.filter(path -> isPropertiesFile(path))
.filter(path -> path.toFile().isFile())
.map(path -> findReferences(path, propertyKey))
.flatMap(Collection::stream)
.collect(Collectors.toList());
try (Stream<Path> walk = Files.walk(workspaceRoot)) {
List<Location> 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;

View File

@@ -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";

View File

@@ -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<WorkspaceFolder> wsFolder(File directory) {
if (directory!=null) {
return ImmutableList.of(new WorkspaceFolder(
directory.toURI().toString(),
directory.getName()
));
}
return ImmutableList.of();
}
public void initialize(Collection<WorkspaceFolder> wsRoots) {
indexer.initialize(wsRoots);
}
}

View File

@@ -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<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(root, "test.property");
CompletableFuture<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "test.property");
assertNotNull(resultFuture);
List<? extends Location> locations = resultFuture.get();
@@ -49,12 +54,22 @@ public class PropertyReferenceFinderTest {
assertEquals(13, location.getRange().getEnd().getCharacter());
}
private Collection<WorkspaceFolder> 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<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(root, "test.property");
CompletableFuture<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "test.property");
assertNotNull(resultFuture);
List<? extends Location> 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<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(root, "server.port");
CompletableFuture<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "server.port");
assertNotNull(resultFuture);
List<? extends Location> 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<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(root, "appl1.prop");
CompletableFuture<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "appl1.prop");
assertNotNull(resultFuture);
List<? extends Location> 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<? extends Location> 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<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(root, "appl1.prop");
CompletableFuture<List<? extends Location>> resultFuture = provider.findReferencesFromPropertyFiles(wsFolder(root), "appl1.prop");
assertNotNull(resultFuture);
List<? extends Location> locations = resultFuture.get();

View File

@@ -177,7 +177,7 @@ public abstract class LaunguageServerApp {
* @return
*/
protected ExecutorService createServerThreads() {
return Executors.newSingleThreadExecutor();
return Executors.newCachedThreadPool();
}
private <T> Launcher<T> createSocketLauncher(Object localService, Class<T> remoteInterface, SocketAddress socketAddress, ExecutorService executorService, Function<MessageConsumer, MessageConsumer> wrapper) throws IOException {

View File

@@ -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);

View File

@@ -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<WorkspaceFolder[]> getWorkspaceFolders();
}

View File

@@ -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;
}
}

View File

@@ -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.

View File

@@ -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;
}
}

View File

@@ -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) + "]";
}
}

View File

@@ -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);
}

View File

@@ -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<InitializeResult> 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<WorkspaceFolder> 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<WorkspaceFolder> 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<WorkspaceFolder> getWorkspaceFolders(InitializeParams params) {
List<WorkspaceFolder> 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<WorkspaceFolder> getWorkspaceRoots() {
return getWorkspaceService().getWorkspaceRoots();
}
// /**
// * Deprecated, shouldn't use and should be removed. Anyone calling this
// * will have problems handling multi-root workspaces.
// * <p>
// * Use getWorkspaceRoots instead.
// */
// @Deprecated
// public Path getWorkspaceRoot() {
// try {
// Optional<WorkspaceFolder> 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);
}
}

View File

@@ -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<WorkspaceFolder> workspaceRoots = new HashSet<>();
private ListenerList<Settings> configurationListeners = new ListenerList<>();
private ExecuteCommandHandler executeCommandHandler;
private WorkspaceSymbolHandler workspaceSymbolHandler;
private SimpleServerFileObserver fileObserver;
private ListenerList<DidChangeWorkspaceFoldersParams> 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<DidChangeWorkspaceFoldersParams> l) {
workspaceFolderListeners.add(l);
}
@Override
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
if (this.executeCommandHandler!=null) {
@@ -123,4 +154,14 @@ public class SimpleWorkspaceService implements WorkspaceService {
fileObserver.dispose();
}
public Collection<WorkspaceFolder> getWorkspaceRoots() {
return ImmutableList.copyOf(workspaceRoots);
}
public synchronized void setWorkspaceFolders(List<WorkspaceFolder> workspaceFolders) {
workspaceRoots = new HashSet<>();
workspaceRoots.addAll(workspaceFolders);
}
}

View File

@@ -48,4 +48,9 @@ public class MavenJavaProject extends AbstractJavaProject {
return classpath.update();
}
@Override
public String toString() {
return "MavenJavaProject("+classpath.getName()+")";
}
}

View File

@@ -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<S extends SimpleLanguageServer> {
}
return CompletableFuture.completedFuture(new ApplyWorkspaceEditResponse(false));
}
@Override
public CompletableFuture<WorkspaceFolder[]> getWorkspaceFolders() {
if (workspaceRoot!=null) {
return CompletableFuture.completedFuture(new WorkspaceFolder[]{
new WorkspaceFolder(workspaceRoot.toURI().toString(), workspaceRoot.getName())
});
}
return CompletableFuture.completedFuture(new WorkspaceFolder[]{});
}
});
}

View File

@@ -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"
}
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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": {