WIP: exploring multi-root workspace protocol in vscode + boot java ls
Note: this code is not working, probably better to start over when the time comes. This branch is saved just in case it helps seeing the kinds of changes we might want/need to do.
This commit is contained in:
@@ -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<InitializeResult> initialize(InitializeParams params) {
|
||||
CompletableFuture<InitializeResult> 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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<Void> initialize(final Path workspaceRoot) {
|
||||
if (workspaceRoot==null) {
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}
|
||||
public CompletableFuture<Void> initialize(Collection<WorkspaceFolder> 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<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())
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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<? extends SymbolInformation> 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<WorkspaceFolder> 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<? extends SymbolInformation> 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<? extends SymbolInformation> symbols = indexer.getSymbols(uriPrefix + "/src/main/java/org/test/SimpleComponent.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<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();
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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.
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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<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) {
|
||||
|
||||
@@ -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<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 +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<DidChangeWorkspaceFoldersParams> l) {
|
||||
workspaceFolderListeners.add(l);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Object> executeCommand(ExecuteCommandParams params) {
|
||||
if (this.executeCommandHandler!=null) {
|
||||
@@ -123,4 +155,14 @@ public class SimpleWorkspaceService implements WorkspaceService {
|
||||
fileObserver.dispose();
|
||||
}
|
||||
|
||||
public Collection<WorkspaceFolder> getWorkspaceRoots() {
|
||||
return ImmutableList.copyOf(workspaceRoots);
|
||||
}
|
||||
|
||||
public synchronized void setRoot(Path path) {
|
||||
workspaceRoots = new HashSet<>();
|
||||
workspaceRoots.add(new WorkspaceFolder(path.toUri().toString(), path.getFileName().toString()));
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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()+")";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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[]{});
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user