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 c6a49313e..3676584e4 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,17 +10,16 @@ *******************************************************************************/ package org.springframework.ide.vscode.boot.java; -import java.io.File; -import java.net.URI; -import java.nio.file.Path; +import java.util.Collections; import java.util.HashMap; import java.util.Map; -import java.util.Optional; 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.autowired.AutowiredHoverProvider; import org.springframework.ide.vscode.boot.java.beans.BeansSymbolProvider; import org.springframework.ide.vscode.boot.java.beans.ComponentSymbolProvider; @@ -59,14 +58,13 @@ 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.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; 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; @@ -186,6 +184,10 @@ public class BootJavaLanguageServer extends SimpleLanguageServer { @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) 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 87314a324..01ddea489 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 @@ -103,14 +103,15 @@ public class SpringIndexer { this.symbols = Collections.synchronizedList(new ArrayList<>()); this.symbolsByDoc = new ConcurrentHashMap<>(); + + server.getWorkspaceService().onDidChangeWorkspaceFolders(evt -> { + System.err.println("Workspace roots have changed!"); + refresh(); + }); } public CompletableFuture initialize(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) { 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 4d9f16a7f..6c097efd9 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleLanguageServer.java @@ -10,11 +10,13 @@ *******************************************************************************/ 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,7 +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; @@ -71,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"); @@ -168,12 +172,24 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl @Override public CompletableFuture initialize(InitializeParams params) { Log.debug("Initializing: "+params); - String rootPath = params.getRootPath(); - if (rootPath==null) { - Log.debug("workspaceRoot NOT SET"); - } else { - this.getWorkspaceService().setRoot(Paths.get(rootPath)); + + // multi-root workspace handling + List workspaceFolders = getWorkspaceFolders(params); + if (!workspaceFolders.isEmpty()) { + this.getWorkspaceService().setWorkspaceFolders(workspaceFolders); } + else { + String rootPath = params.getRootPath(); + if (rootPath==null) { + Log.debug("workspaceRoot NOT SET"); + } else { + List singleRootFolder = new ArrayList<>(); + Path path = Paths.get(rootPath); + singleRootFolder.add(new WorkspaceFolder(path.toUri().toString(), path.getFileName().toString())); + this.getWorkspaceService().setWorkspaceFolders(singleRootFolder); + } + } + this.hasCompletionSnippetSupport = safeGet(false, () -> params.getCapabilities().getTextDocument().getCompletion().getCompletionItem().getSnippetSupport()); this.hasExecuteCommandSupport = safeGet(false, () -> params.getCapabilities().getWorkspace().getExecuteCommand()!=null); this.hasFileWatcherRegistrationSupport = safeGet(false, () -> params.getCapabilities().getWorkspace().getDidChangeWatchedFiles().getDynamicRegistration()); @@ -192,6 +208,31 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl return CompletableFuture.completedFuture(result); } + private List getWorkspaceFolders(InitializeParams params) { + List initialFolders = new ArrayList<>(); + + Object initOptions = params.getInitializationOptions(); + if (initOptions != null && initOptions instanceof Map) { + Map initializationOptions = (Map) initOptions; + Object folders = initializationOptions.get("workspaceFolders"); + if (folders != null && folders instanceof List) { + List workspaceFolders = (List) folders; + for (Object object : workspaceFolders) { + String folderPath = object.toString(); + String folderName = null; + + int folderNameStart = folderPath.lastIndexOf("/"); + if (folderNameStart > 0) { + folderName = folderPath.substring(folderPath.lastIndexOf("/") + 1); + } + initialFolders.add(new WorkspaceFolder(object.toString(), folderName)); + } + } + } + + return initialFolders; + } + /** * Get some info safely. If there's any kind of exception, ignore it * and retutn default value instead. @@ -485,4 +526,10 @@ public abstract class SimpleLanguageServer implements LanguageServer, LanguageCl public boolean canRegisterFileWatchersDynamically() { return hasFileWatcherRegistrationSupport; } + + @Override + public void didChangeWorkspaceFolders(DidChangeWorkspaceFoldersParams params) { + getWorkspaceService().didChangeWorkspaceFolders(params); + } + } diff --git a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java index 3e6003914..2463db6e1 100644 --- a/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java +++ b/headless-services/commons/commons-language-server/src/main/java/org/springframework/ide/vscode/commons/languageserver/util/SimpleWorkspaceService.java @@ -10,7 +10,6 @@ *******************************************************************************/ 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; @@ -159,9 +158,9 @@ public class SimpleWorkspaceService implements WorkspaceService, WorkspaceFolder return ImmutableList.copyOf(workspaceRoots); } - public synchronized void setRoot(Path path) { + public synchronized void setWorkspaceFolders(List workspaceFolders) { workspaceRoots = new HashSet<>(); - workspaceRoots.add(new WorkspaceFolder(path.toUri().toString(), path.getFileName().toString())); + workspaceRoots.addAll(workspaceFolders); } diff --git a/vscode-extensions/commons-vscode/package.json b/vscode-extensions/commons-vscode/package.json index ce13187be..fec38eee4 100644 --- a/vscode-extensions/commons-vscode/package.json +++ b/vscode-extensions/commons-vscode/package.json @@ -30,6 +30,6 @@ "typescript": "^2.3.0", "@types/node": "^6.0.68", "vscode": "^1.1.5", - "vscode-languageclient": "^3.4.2" + "vscode-languageclient": "^3.4.5" } } diff --git a/vscode-extensions/vscode-boot-java/lib/Main.ts b/vscode-extensions/vscode-boot-java/lib/Main.ts index 78f0d4c5d..d94520247 100644 --- a/vscode-extensions/vscode-boot-java/lib/Main.ts +++ b/vscode-extensions/vscode-boot-java/lib/Main.ts @@ -10,7 +10,7 @@ import * as FS from 'fs'; import * as Net from 'net'; import * as ChildProcess from 'child_process'; import { LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, StreamInfo } from 'vscode-languageclient'; -import { TextDocument } from 'vscode'; +import { workspace, TextDocument } from 'vscode'; import { Trace } from 'vscode-jsonrpc'; import * as commons from 'commons-vscode'; @@ -38,7 +38,10 @@ export function activate(context: VSCode.ExtensionContext) { documentSelector: ['java'], synchronize: { configurationSection: 'boot-java' - } + }, + initializationOptions: { + workspaceFolders: workspace.workspaceFolders ? workspace.workspaceFolders.map(f => f.uri.toString()) : null + }, } }; commons.activate(options, context); diff --git a/vscode-extensions/vscode-boot-java/package.json b/vscode-extensions/vscode-boot-java/package.json index 4bb7d2cee..a50ce5f27 100644 --- a/vscode-extensions/vscode-boot-java/package.json +++ b/vscode-extensions/vscode-boot-java/package.json @@ -48,7 +48,7 @@ "vsce-package": "vsce package" }, "dependencies": { - "vscode-languageclient": "^3.4.2", + "vscode-languageclient": "^3.4.5", "commons-vscode": "^0.0.6" }, "devDependencies": {