diff --git a/wsagent/che-core-api-languageserver/pom.xml b/wsagent/che-core-api-languageserver/pom.xml
index b49cd12a3bf..6a145fc2b6e 100644
--- a/wsagent/che-core-api-languageserver/pom.xml
+++ b/wsagent/che-core-api-languageserver/pom.xml
@@ -69,10 +69,22 @@
org.eclipse.che.core
che-core-api-languageserver-shared
+
+ org.eclipse.che.core
+ che-core-api-model
+
org.eclipse.che.core
che-core-api-project
+
+ org.eclipse.che.core
+ che-core-api-workspace
+
+
+ org.eclipse.che.core
+ che-core-api-workspace-shared
+
org.eclipse.che.core
che-core-commons-lang
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java
index 5d93af01997..6abbdb7df14 100644
--- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/LanguageServerModule.java
@@ -22,6 +22,7 @@
import org.eclipse.che.api.languageserver.registry.LanguageServerRegistryImpl;
import org.eclipse.che.api.languageserver.registry.ServerInitializer;
import org.eclipse.che.api.languageserver.registry.ServerInitializerImpl;
+import org.eclipse.che.api.languageserver.remote.LsRemoteModule;
import org.eclipse.che.api.languageserver.service.LanguageRegistryService;
import org.eclipse.che.api.languageserver.service.LanguageServerInitializationHandler;
import org.eclipse.che.api.languageserver.service.TextDocumentService;
@@ -32,6 +33,8 @@ public class LanguageServerModule extends AbstractModule {
@Override
protected void configure() {
+ install(new LsRemoteModule());
+
bind(LanguageServerRegistry.class).to(LanguageServerRegistryImpl.class);
bind(ServerInitializer.class).to(ServerInitializerImpl.class);
bind(LanguageRegistryService.class);
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java
index ac59b14e357..7451787e8ee 100644
--- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/launcher/LanguageServerLauncher.java
@@ -31,4 +31,13 @@ public interface LanguageServerLauncher {
/** Indicates if language server is installed and is ready to be started. */
boolean isAbleToLaunch();
+
+ /**
+ * Denotes if the language server will be launched in a local environment or remote (i.e. if
+ * isLocal
returns true
than the server is launched in the local
+ * physical/virtual machine, otherwise means that the server is launched in the remote machine)
+ */
+ default boolean isLocal() {
+ return true;
+ }
}
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java
index ea55397962a..1082701dcd3 100644
--- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImpl.java
@@ -10,12 +10,15 @@
*/
package org.eclipse.che.api.languageserver.registry;
+import static javax.ws.rs.core.UriBuilder.fromUri;
import static org.eclipse.che.api.fs.server.WsPathUtils.absolutize;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
+import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
@@ -24,15 +27,22 @@
import java.util.stream.Collectors;
import javax.annotation.PreDestroy;
import javax.inject.Inject;
+import javax.inject.Named;
import javax.inject.Provider;
import javax.inject.Singleton;
+import org.eclipse.che.api.core.ApiException;
+import org.eclipse.che.api.core.model.workspace.Workspace;
import org.eclipse.che.api.core.notification.EventService;
+import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
import org.eclipse.che.api.languageserver.exception.LanguageServerException;
import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
+import org.eclipse.che.api.languageserver.remote.RemoteLsLauncherProvider;
import org.eclipse.che.api.languageserver.service.LanguageServiceUtils;
import org.eclipse.che.api.languageserver.shared.model.LanguageDescription;
import org.eclipse.che.api.project.server.ProjectManager;
import org.eclipse.che.api.project.server.impl.RegisteredProject;
+import org.eclipse.che.api.workspace.server.WorkspaceService;
+import org.eclipse.che.api.workspace.shared.dto.WorkspaceDto;
import org.eclipse.lsp4j.MessageParams;
import org.eclipse.lsp4j.MessageType;
import org.eclipse.lsp4j.ServerCapabilities;
@@ -44,6 +54,11 @@
public class LanguageServerRegistryImpl implements LanguageServerRegistry {
private static final Logger LOG = LoggerFactory.getLogger(LanguageServerRegistryImpl.class);
+
+ private final String workspaceId;
+ private final String apiEndpoint;
+ private final HttpJsonRequestFactory httpJsonRequestFactory;
+ private final Set launcherProviders;
private final List languages;
private final List launchers;
private final AtomicInteger serverId = new AtomicInteger();
@@ -57,15 +72,24 @@ public class LanguageServerRegistryImpl implements LanguageServerRegistry {
private final ServerInitializer initializer;
private EventService eventService;
private CheLanguageClientFactory clientFactory;
+ private Workspace workspace;
@Inject
public LanguageServerRegistryImpl(
+ @Named("env.CHE_WORKSPACE_ID") String workspaceId,
+ @Named("che.api") String apiEndpoint,
+ HttpJsonRequestFactory httpJsonRequestFactory,
+ Set launcherProviders,
Set languageServerLaunchers,
Set languages,
Provider projectManagerProvider,
ServerInitializer initializer,
EventService eventService,
CheLanguageClientFactory clientFactory) {
+ this.workspaceId = workspaceId;
+ this.apiEndpoint = apiEndpoint;
+ this.httpJsonRequestFactory = httpJsonRequestFactory;
+ this.launcherProviders = launcherProviders;
this.languages = new ArrayList<>(languages);
this.launchers = new ArrayList<>(languageServerLaunchers);
this.projectManagerProvider = projectManagerProvider;
@@ -189,8 +213,16 @@ private List findLaunchers(String projectPath, String fi
if (language == null) {
return Collections.emptyList();
}
+ List combinedLaunchers = new LinkedList<>(launchers);
+ Workspace workspace = getWorkspaceConfiguration();
+ if (workspace != null) {
+ for (RemoteLsLauncherProvider launcherProvider : launcherProviders) {
+ combinedLaunchers.addAll(launcherProvider.getAll(workspace));
+ }
+ }
+
List result = new ArrayList<>();
- for (LanguageServerLauncher launcher : launchers) {
+ for (LanguageServerLauncher launcher : combinedLaunchers) {
if (launcher.isAbleToLaunch()) {
int score = matchScore(launcher.getDescription(), fileUri, language.getLanguageId());
if (score > 0) {
@@ -345,4 +377,25 @@ public InitializedLanguageServer getServer(String id) {
}
return null;
}
+
+ private Workspace getWorkspaceConfiguration() {
+ if (workspace != null) {
+ return workspace;
+ }
+
+ String href =
+ fromUri(apiEndpoint)
+ .path(WorkspaceService.class)
+ .path(WorkspaceService.class, "getByKey")
+ .queryParam("includeInternalServers", true)
+ .build(workspaceId)
+ .toString();
+ try {
+ return workspace =
+ httpJsonRequestFactory.fromUrl(href).useGetMethod().request().asDto(WorkspaceDto.class);
+ } catch (IOException | ApiException e) {
+ LOG.error("Did not manage to get workspace configuration: {}", workspaceId, e);
+ return null;
+ }
+ }
}
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java
index 7f197a287ed..1ff8bc27914 100644
--- a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/registry/ServerInitializerImpl.java
@@ -92,7 +92,7 @@ public void removeObserver(ServerInitializerObserver observer) {
public CompletableFuture> initialize(
LanguageServerLauncher launcher, LanguageClient client, String projectPath)
throws LanguageServerException {
- InitializeParams initializeParams = prepareInitializeParams(projectPath);
+ InitializeParams initializeParams = prepareInitializeParams(launcher, projectPath);
String launcherId = launcher.getDescription().getId();
CompletableFuture> result =
new CompletableFuture>();
@@ -142,9 +142,16 @@ protected void registerCallbacks(LanguageServer server, LanguageServerLauncher l
}
}
- private InitializeParams prepareInitializeParams(String projectPath) {
+ private InitializeParams prepareInitializeParams(
+ LanguageServerLauncher launcher, String projectPath) {
InitializeParams initializeParams = new InitializeParams();
- initializeParams.setProcessId(PROCESS_ID);
+
+ if (launcher.isLocal()) {
+ initializeParams.setProcessId(PROCESS_ID);
+ } else {
+ initializeParams.setProcessId(null);
+ }
+
initializeParams.setRootPath(LanguageServiceUtils.removeUriScheme(projectPath));
initializeParams.setRootUri(projectPath);
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationDetector.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationDetector.java
new file mode 100644
index 00000000000..6021cf46d63
--- /dev/null
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationDetector.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2012-2017 Red Hat, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.languageserver.remote;
+
+import java.util.Map;
+import javax.inject.Singleton;
+
+/** Detects if machine server attributes indicates that we are dealing with language server. */
+@Singleton
+class LsConfigurationDetector {
+ /**
+ * Tests attributes for a language server indicator
+ *
+ * @param attributes map with machine server attributes
+ * @return true if language server is detected, false otherwise
+ */
+ boolean isDetected(Map attributes) {
+ return "ls".equals(attributes.get("type"));
+ }
+}
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractor.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractor.java
new file mode 100644
index 00000000000..77eae160671
--- /dev/null
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractor.java
@@ -0,0 +1,127 @@
+/*
+ * Copyright (c) 2012-2017 Red Hat, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.languageserver.remote;
+
+import static java.util.Collections.emptyList;
+import static java.util.Collections.unmodifiableList;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.eclipse.che.api.languageserver.registry.DocumentFilter;
+import org.eclipse.che.api.languageserver.registry.LanguageServerDescription;
+
+/**
+ * This class is responsible for language server description extraction out of server attributes
+ * map. It is expected that there will be specific attribute named config
that will
+ * contain serialized json data that represents all language server configuration data. Structure of
+ * json corresponds to {@link LanguageServerDescription} class with all aggregated classes
+ */
+@Singleton
+class LsConfigurationExtractor {
+ private final JsonParser jsonParser;
+
+ @Inject
+ LsConfigurationExtractor(JsonParser jsonParser) {
+ this.jsonParser = jsonParser;
+ }
+
+ LanguageServerDescription extract(Map attributes) {
+ String config = attributes.get("config");
+ JsonObject configJsonObject = jsonParser.parse(config).getAsJsonObject();
+ String id = getId(configJsonObject);
+ List languageIds = getLanguageIds(configJsonObject);
+ List fileWatchPatterns = getFileWatchPatterns(configJsonObject);
+ List documentFilters = getDocumentFilters(configJsonObject);
+
+ return new LanguageServerDescription(id, languageIds, documentFilters, fileWatchPatterns);
+ }
+
+ private String getId(JsonObject jsonObject) {
+ return !jsonObject.has("id") ? null : jsonObject.get("id").getAsString();
+ }
+
+ private List getLanguageIds(JsonObject jsonObject) {
+ if (!jsonObject.has("languageIds")) {
+ return emptyList();
+ }
+
+ JsonArray languageIdsJsonArray = jsonObject.get("languageIds").getAsJsonArray();
+ int size = languageIdsJsonArray.size();
+ List languageIds = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ String languageId = languageIdsJsonArray.get(i).getAsString();
+ languageIds.add(languageId);
+ }
+
+ return unmodifiableList(languageIds);
+ }
+
+ private List getFileWatchPatterns(JsonObject jsonObject) {
+ if (!jsonObject.has("fileWatchPatterns")) {
+ return emptyList();
+ }
+
+ JsonArray fileWatchPatternsJsonArray = jsonObject.get("fileWatchPatterns").getAsJsonArray();
+ int size = fileWatchPatternsJsonArray.size();
+ List fileWatchPatterns = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ String fileWatchPattern = fileWatchPatternsJsonArray.get(i).getAsString();
+ fileWatchPatterns.add(fileWatchPattern);
+ }
+
+ return unmodifiableList(fileWatchPatterns);
+ }
+
+ private List getDocumentFilters(JsonObject jsonObject) {
+ if (!jsonObject.has("documentFilters")) {
+ return emptyList();
+ }
+ JsonArray documentFiltersJsonArray = jsonObject.get("documentFilters").getAsJsonArray();
+
+ int size = documentFiltersJsonArray.size();
+ List documentFilters = new ArrayList<>(size);
+ for (int i = 0; i < size; i++) {
+ JsonObject documentFilterJsonObject = documentFiltersJsonArray.get(i).getAsJsonObject();
+
+ String pathRegex;
+ if (documentFilterJsonObject.has("pathRegex")) {
+ pathRegex = documentFilterJsonObject.get("pathRegex").getAsString();
+ } else {
+ pathRegex = null;
+ }
+
+ String languageId;
+ if (documentFilterJsonObject.has("languageId")) {
+ languageId = documentFilterJsonObject.get("languageId").getAsString();
+ } else {
+ languageId = null;
+ }
+
+ String schema;
+ if (documentFilterJsonObject.has("scheme")) {
+ schema = documentFilterJsonObject.get("scheme").getAsString();
+ } else {
+ schema = null;
+ }
+
+ DocumentFilter documentFilter = new DocumentFilter(languageId, pathRegex, schema);
+ documentFilters.add(documentFilter);
+ }
+
+ return unmodifiableList(documentFilters);
+ }
+}
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsRemoteModule.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsRemoteModule.java
new file mode 100644
index 00000000000..daecb5d2cc7
--- /dev/null
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/LsRemoteModule.java
@@ -0,0 +1,24 @@
+/*
+ * Copyright (c) 2012-2017 Red Hat, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.languageserver.remote;
+
+import com.google.inject.AbstractModule;
+import com.google.inject.multibindings.Multibinder;
+
+public class LsRemoteModule extends AbstractModule {
+
+ @Override
+ protected void configure() {
+ Multibinder.newSetBinder(binder(), RemoteLsLauncherProvider.class)
+ .addBinding()
+ .to(SocketLsLauncherProvider.class);
+ }
+}
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/RemoteLsLauncherProvider.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/RemoteLsLauncherProvider.java
new file mode 100644
index 00000000000..a337e4cfdb7
--- /dev/null
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/RemoteLsLauncherProvider.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (c) 2012-2017 Red Hat, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.languageserver.remote;
+
+import java.util.Set;
+import org.eclipse.che.api.core.model.workspace.Workspace;
+import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
+
+/**
+ * Provides remote language server launcher instances constructed according to workspace
+ * configuration. The launchers (depending on context) may in fact not physically launch the
+ * language servers but establish a remote connection to already launched servers.
+ */
+public interface RemoteLsLauncherProvider {
+ /**
+ * Get all remote language server launchers that are mentioned in a workspace configuration.
+ *
+ * @param workspace workspace configuration
+ * @return set of language server launchers
+ */
+ Set getAll(Workspace workspace);
+}
diff --git a/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/SocketLsLauncherProvider.java b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/SocketLsLauncherProvider.java
new file mode 100644
index 00000000000..d63895831e4
--- /dev/null
+++ b/wsagent/che-core-api-languageserver/src/main/java/org/eclipse/che/api/languageserver/remote/SocketLsLauncherProvider.java
@@ -0,0 +1,152 @@
+/*
+ * Copyright (c) 2012-2017 Red Hat, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.languageserver.remote;
+
+import static java.util.Collections.emptySet;
+import static java.util.Collections.unmodifiableSet;
+import static org.slf4j.LoggerFactory.getLogger;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.Socket;
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import javax.inject.Inject;
+import javax.inject.Singleton;
+import org.eclipse.che.api.core.model.workspace.Runtime;
+import org.eclipse.che.api.core.model.workspace.Workspace;
+import org.eclipse.che.api.core.model.workspace.runtime.Machine;
+import org.eclipse.che.api.core.model.workspace.runtime.Server;
+import org.eclipse.che.api.languageserver.exception.LanguageServerException;
+import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
+import org.eclipse.che.api.languageserver.registry.LanguageServerDescription;
+import org.eclipse.lsp4j.jsonrpc.Launcher;
+import org.eclipse.lsp4j.services.LanguageClient;
+import org.eclipse.lsp4j.services.LanguageServer;
+import org.slf4j.Logger;
+
+/** Provides socket based language server launchers */
+@Singleton
+class SocketLsLauncherProvider implements RemoteLsLauncherProvider {
+ private static final Logger LOG = getLogger(SocketLsLauncherProvider.class);
+
+ private final LsConfigurationDetector lsConfigurationDetector;
+ private final LsConfigurationExtractor lsConfigurationExtractor;
+
+ private final Map lslRegistry = new ConcurrentHashMap<>();
+
+ @Inject
+ public SocketLsLauncherProvider(
+ LsConfigurationDetector lsConfigurationDetector,
+ LsConfigurationExtractor lsConfigurationExtractor) {
+ this.lsConfigurationDetector = lsConfigurationDetector;
+ this.lsConfigurationExtractor = lsConfigurationExtractor;
+ }
+
+ @Override
+ public Set getAll(Workspace workspace) {
+ Runtime runtime = workspace.getRuntime();
+ if (runtime == null) {
+ return emptySet();
+ }
+
+ for (Map.Entry machineEntry : runtime.getMachines().entrySet()) {
+ String machineName = machineEntry.getKey();
+ Machine machine = machineEntry.getValue();
+ Map servers = machine.getServers();
+
+ for (Map.Entry serverEntry : servers.entrySet()) {
+ String serverName = serverEntry.getKey();
+ Server server = serverEntry.getValue();
+ String serverUrl = server.getUrl();
+ Map serverAttributes = server.getAttributes();
+
+ if (lslRegistry.keySet().contains(machineName + serverName)) {
+ continue;
+ }
+
+ if (!lsConfigurationDetector.isDetected(serverAttributes)) {
+ continue;
+ }
+
+ LanguageServerDescription description = lsConfigurationExtractor.extract(serverAttributes);
+
+ try {
+ URI uri = new URI(serverUrl);
+ String host = uri.getHost();
+ int port = uri.getPort();
+
+ SocketLanguageServerLauncher launcher =
+ new SocketLanguageServerLauncher(description, host, port);
+ lslRegistry.put(machineName + serverName, launcher);
+ } catch (URISyntaxException e) {
+ LOG.error("Can't parse server url: {}", serverUrl, e);
+ }
+ }
+ }
+
+ return unmodifiableSet(new HashSet<>(lslRegistry.values()));
+ }
+
+ private static final class SocketLanguageServerLauncher implements LanguageServerLauncher {
+
+ private final LanguageServerDescription languageServerDescription;
+ private final String host;
+ private final int port;
+
+ SocketLanguageServerLauncher(
+ LanguageServerDescription languageServerDescription, String host, int port) {
+ this.languageServerDescription = languageServerDescription;
+ this.host = host;
+ this.port = port;
+ }
+
+ @Override
+ public LanguageServer launch(String projectPath, LanguageClient client)
+ throws LanguageServerException {
+ try {
+ Socket socket = new Socket(host, port);
+ socket.setKeepAlive(true);
+ InputStream inputStream = socket.getInputStream();
+ OutputStream outputStream = socket.getOutputStream();
+
+ Launcher launcher =
+ Launcher.createLauncher(client, LanguageServer.class, inputStream, outputStream);
+
+ launcher.startListening();
+ return launcher.getRemoteProxy();
+ } catch (IOException e) {
+ throw new LanguageServerException(
+ "Can't launch language server for project: " + projectPath, e);
+ }
+ }
+
+ @Override
+ public boolean isLocal() {
+ return false;
+ }
+
+ @Override
+ public LanguageServerDescription getDescription() {
+ return languageServerDescription;
+ }
+
+ @Override
+ public boolean isAbleToLaunch() {
+ return host != null && languageServerDescription != null;
+ }
+ }
+}
diff --git a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java
index 36d9e65fbf7..f1907928fbb 100644
--- a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java
+++ b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/registry/LanguageServerRegistryImplTest.java
@@ -21,8 +21,13 @@
import static org.testng.Assert.assertNotNull;
import java.util.Collections;
+import java.util.HashSet;
import java.util.concurrent.CompletableFuture;
import javax.inject.Provider;
+import org.eclipse.che.api.core.model.workspace.Workspace;
+import org.eclipse.che.api.core.rest.HttpJsonRequest;
+import org.eclipse.che.api.core.rest.HttpJsonRequestFactory;
+import org.eclipse.che.api.core.rest.HttpJsonResponse;
import org.eclipse.che.api.languageserver.exception.LanguageServerException;
import org.eclipse.che.api.languageserver.launcher.LanguageServerLauncher;
import org.eclipse.che.api.languageserver.shared.model.LanguageDescription;
@@ -57,6 +62,10 @@ public class LanguageServerRegistryImplTest {
@Mock private ProjectManager pm;
@Mock private CheLanguageClientFactory clientFactory;
@Mock private CheLanguageClient languageClient;
+ @Mock private HttpJsonRequestFactory httpJsonRequestFactory;
+ @Mock private HttpJsonRequest httpJsonRequest;
+ @Mock private HttpJsonResponse httpJsonResponse;
+ @Mock private Workspace workspace;
private LanguageServerRegistryImpl registry;
private LanguageServerDescription serverDescription;
@@ -66,6 +75,7 @@ public class LanguageServerRegistryImplTest {
@BeforeMethod
public void setUp() throws Exception {
+
this.serverCapabilities = new ServerCapabilities();
serverDescription =
new LanguageServerDescription(
@@ -76,6 +86,7 @@ public void setUp() throws Exception {
when(languageServerLauncher.isAbleToLaunch()).thenReturn(true);
when(languageServerLauncher.getDescription()).thenReturn(serverDescription);
+ when(languageServerLauncher.isLocal()).thenReturn(true);
when(languageDescription.getLanguageId()).thenReturn("id");
when(languageDescription.getFileExtensions()).thenReturn(Collections.singletonList("txt"));
when(languageDescription.getMimeType()).thenReturn("plain/text");
@@ -87,9 +98,18 @@ public void setUp() throws Exception {
when(clientFactory.create(anyString())).thenReturn(languageClient);
+ when(httpJsonRequestFactory.fromUrl(any(String.class))).thenReturn(httpJsonRequest);
+ when(httpJsonRequest.useGetMethod()).thenReturn(httpJsonRequest);
+ when(httpJsonRequest.request()).thenReturn(httpJsonResponse);
+ when(httpJsonResponse.asDto(any())).thenReturn(workspace);
+
registry =
spy(
new LanguageServerRegistryImpl(
+ "",
+ "",
+ httpJsonRequestFactory,
+ new HashSet<>(),
Collections.singleton(languageServerLauncher),
Collections.singleton(languageDescription),
pmp,
diff --git a/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractorTest.java b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractorTest.java
new file mode 100644
index 00000000000..69867e7ad86
--- /dev/null
+++ b/wsagent/che-core-api-languageserver/src/test/java/org/eclipse/che/api/languageserver/remote/LsConfigurationExtractorTest.java
@@ -0,0 +1,191 @@
+/*
+ * Copyright (c) 2012-2017 Red Hat, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * Red Hat, Inc. - initial API and implementation
+ */
+package org.eclipse.che.api.languageserver.remote;
+
+import static org.testng.Assert.*;
+
+import com.google.gson.JsonParser;
+import java.util.Collections;
+import java.util.Map;
+import org.eclipse.che.api.languageserver.registry.LanguageServerDescription;
+import org.mockito.testng.MockitoTestNGListener;
+import org.testng.annotations.BeforeMethod;
+import org.testng.annotations.Listeners;
+import org.testng.annotations.Test;
+
+/** Tests for {@link LsConfigurationExtractor} */
+@Listeners(MockitoTestNGListener.class)
+public class LsConfigurationExtractorTest {
+
+ private LsConfigurationExtractor lsConfigurationExtractor;
+
+ @BeforeMethod
+ public void setUp() throws Exception {
+ lsConfigurationExtractor = new LsConfigurationExtractor(new JsonParser());
+ }
+
+ @Test
+ public void shouldExtractId() throws Exception {
+ Map attributes = Collections.singletonMap("config", "{\"id\":\"testId\"}");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertEquals(languageServerDescription.getId(), "testId");
+ }
+
+ @Test
+ public void shouldExtractNullWhenIdIsNotMentioned() throws Exception {
+ Map attributes = Collections.singletonMap("config", "{}");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertNull(languageServerDescription.getId());
+ }
+
+ @Test
+ public void shouldExtractLanguageIds() throws Exception {
+ Map attributes =
+ Collections.singletonMap("config", "{\"languageIds\": [\"languageId\"] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertEquals(
+ languageServerDescription.getLanguageIds(), Collections.singletonList("languageId"));
+ }
+
+ @Test
+ public void shouldExtractEmptyLanguageIdsForEmptyArray() throws Exception {
+ Map attributes = Collections.singletonMap("config", "{\"languageIds\": [] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertTrue(languageServerDescription.getLanguageIds().isEmpty());
+ }
+
+ @Test
+ public void shouldExtractEmptyLanguageIdsWhenLanguageIdsAreNotMentioned() throws Exception {
+ Map attributes = Collections.singletonMap("config", "{ }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertTrue(languageServerDescription.getLanguageIds().isEmpty());
+ }
+
+ @Test
+ public void shouldExtractFileWatchPatterns() throws Exception {
+ Map attributes =
+ Collections.singletonMap("config", "{\"fileWatchPatterns\": [\"fileWatchPattern\"] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertEquals(
+ languageServerDescription.getFileWatchPatterns(),
+ Collections.singletonList("fileWatchPattern"));
+ }
+
+ @Test
+ public void shouldExtractEmptyFileWatchPatternsForEmptyArray() throws Exception {
+ Map attributes =
+ Collections.singletonMap("config", "{\"fileWatchPatterns\": [] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertTrue(languageServerDescription.getFileWatchPatterns().isEmpty());
+ }
+
+ @Test
+ public void shouldExtractEmptyFileWatchPatternsWhenfileWatchPatternsAreNotMentioned()
+ throws Exception {
+ Map attributes = Collections.singletonMap("config", "{ }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertTrue(languageServerDescription.getFileWatchPatterns().isEmpty());
+ }
+
+ @Test
+ public void shouldExtractDocumentFilter() throws Exception {
+ Map attributes =
+ Collections.singletonMap("config", "{\"documentFilters\": [{}] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertEquals(languageServerDescription.getDocumentFilters().size(), 1);
+ }
+
+ @Test
+ public void shouldExtractEmptyDocumentFilterForEmptyArray() throws Exception {
+ Map attributes =
+ Collections.singletonMap("config", "{\"documentFilters\": [] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertTrue(languageServerDescription.getDocumentFilters().isEmpty());
+ }
+
+ @Test
+ public void shouldExtractDocumentFilterWhenDocumentFilterAreNotMentioned() throws Exception {
+ Map attributes = Collections.singletonMap("config", "{ }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertTrue(languageServerDescription.getDocumentFilters().isEmpty());
+ }
+
+ @Test
+ public void shouldExtractDocumentFilterLanguageId() throws Exception {
+ Map attributes =
+ Collections.singletonMap("config", "{\"documentFilters\": [{\"languageId\":\"testId\"}] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertEquals(languageServerDescription.getDocumentFilters().size(), 1);
+ assertEquals(languageServerDescription.getDocumentFilters().get(0).getLanguageId(), "testId");
+ }
+
+ @Test
+ public void shouldExtractDocumentFilterScheme() throws Exception {
+ Map attributes =
+ Collections.singletonMap("config", "{\"documentFilters\": [{\"scheme\":\"testScheme\"}] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertEquals(languageServerDescription.getDocumentFilters().size(), 1);
+ assertEquals(languageServerDescription.getDocumentFilters().get(0).getScheme(), "testScheme");
+ }
+
+ @Test
+ public void shouldExtractDocumentFilterPathRegex() throws Exception {
+ Map attributes =
+ Collections.singletonMap(
+ "config", "{\"documentFilters\": [{\"pathRegex\":\"testPathRegex\"}] }");
+
+ LanguageServerDescription languageServerDescription =
+ lsConfigurationExtractor.extract(attributes);
+
+ assertEquals(languageServerDescription.getDocumentFilters().size(), 1);
+ assertEquals(
+ languageServerDescription.getDocumentFilters().get(0).getPathRegex(), "testPathRegex");
+ }
+}