blob: ec1f3a6703e20e1430b05e5f621f5d29623e7169 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2016 Red Hat Inc. 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:
* Mickael Istria (Red Hat Inc.) - initial implementation
*******************************************************************************/
package org.eclipse.lsp4e;
import java.io.IOException;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.stream.Collectors;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.content.IContentType;
import org.eclipse.debug.core.ILaunchConfiguration;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jface.preference.IPersistentPreferenceStore;
import org.eclipse.jface.preference.IPreferenceStore;
import org.eclipse.lsp4e.server.StreamConnectionProvider;
/**
* This registry aims at providing a good language server connection (as {@link StreamConnectionProvider}
* for a given input.
* At the moment, registry content are hardcoded but we'll very soon need a way
* to contribute to it via plugin.xml (for plugin developers) and from Preferences
* (for end-users to directly register a new server).
*
*/
public class LanguageServersRegistry {
private static final String CONTENT_TYPE_TO_LSP_LAUNCH_PREF_KEY = "contentTypeToLSPLauch"; //$NON-NLS-1$
private static final String EXTENSION_POINT_ID = LanguageServerPlugin.PLUGIN_ID + ".languageServer"; //$NON-NLS-1$
private static final String LS_ELEMENT = "server"; //$NON-NLS-1$
private static final String MAPPING_ELEMENT = "contentTypeMapping"; //$NON-NLS-1$
private static final String ID_ATTRIBUTE = "id"; //$NON-NLS-1$
private static final String CONTENT_TYPE_ATTRIBUTE = "contentType"; //$NON-NLS-1$
private static final String CLASS_ATTRIBUTE = "class"; //$NON-NLS-1$
private static final String LABEL_ATTRIBUTE = "label"; //$NON-NLS-1$
public static abstract class LanguageServerDefinition {
private final @NonNull String id;
private final @NonNull String label;
public LanguageServerDefinition(@NonNull String id, @NonNull String label) {
this.id = id;
this.label = label;
}
public String getId() {
return id;
}
public String getLabel() {
return label;
}
public abstract StreamConnectionProvider createConnectionProvider();
}
static class ExtensionLanguageServerDefinition extends LanguageServerDefinition {
private IConfigurationElement extension;
public ExtensionLanguageServerDefinition(IConfigurationElement element) {
super(element.getAttribute(ID_ATTRIBUTE), element.getAttribute(LABEL_ATTRIBUTE));
this.extension = element;
}
@Override
public StreamConnectionProvider createConnectionProvider() {
try {
return (StreamConnectionProvider) extension.createExecutableExtension(CLASS_ATTRIBUTE);
} catch (CoreException e) {
return null;
}
}
}
static class LaunchConfigurationLanguageServerDefinition extends LanguageServerDefinition {
final ILaunchConfiguration launchConfiguration;
final Set<String> launchModes;
public LaunchConfigurationLanguageServerDefinition(ILaunchConfiguration launchConfiguration,
Set<String> launchModes) {
super(launchConfiguration.getName(), launchConfiguration.getName());
this.launchConfiguration = launchConfiguration;
this.launchModes = launchModes;
}
@Override
public StreamConnectionProvider createConnectionProvider() {
return new LaunchConfigurationStreamProvider(this.launchConfiguration, launchModes);
}
}
private static LanguageServersRegistry INSTANCE = null;
public static LanguageServersRegistry getInstance() {
if (INSTANCE == null) {
INSTANCE = new LanguageServersRegistry();
}
return INSTANCE;
}
private List<ContentTypeToLanguageServerDefinition> connections = new ArrayList<>();
private IPreferenceStore preferenceStore;
private LanguageServersRegistry() {
this.preferenceStore = LanguageServerPlugin.getDefault().getPreferenceStore();
initialize();
}
private void initialize() {
String prefs = preferenceStore.getString(CONTENT_TYPE_TO_LSP_LAUNCH_PREF_KEY);
if (prefs != null && !prefs.isEmpty()) {
String[] entries = prefs.split(","); //$NON-NLS-1$
for (String entry : entries) {
ContentTypeToLSPLaunchConfigEntry mapping = ContentTypeToLSPLaunchConfigEntry.readFromPreference(entry);
if (mapping != null) {
connections.add(mapping);
}
}
}
Map<String, LanguageServerDefinition> servers = new HashMap<>();
List<Entry<IContentType, String>> contentTypes = new ArrayList<>();
for (IConfigurationElement extension : Platform.getExtensionRegistry().getConfigurationElementsFor(EXTENSION_POINT_ID)) {
String id = extension.getAttribute(ID_ATTRIBUTE);
if (id != null && !id.isEmpty()) {
if (extension.getName().equals(LS_ELEMENT)) {
servers.put(id, new ExtensionLanguageServerDefinition(extension));
} else if (extension.getName().equals(MAPPING_ELEMENT)) {
IContentType contentType = Platform.getContentTypeManager().getContentType(extension.getAttribute(CONTENT_TYPE_ATTRIBUTE));
if (contentType != null) {
contentTypes.add(new SimpleEntry<>(contentType, id));
}
}
}
}
for (Entry<IContentType, String> entry : contentTypes) {
IContentType contentType = entry.getKey();
LanguageServerDefinition lsDefinition = servers.get(entry.getValue());
if (lsDefinition != null) {
registerAssociation(contentType, lsDefinition);
} else {
LanguageServerPlugin.logWarning("server '" + entry.getValue() + "' not available", null); //$NON-NLS-1$ //$NON-NLS-2$
}
}
}
private void persistContentTypeToLaunchConfigurationMapping() {
StringBuilder builder = new StringBuilder();
for (ContentTypeToLSPLaunchConfigEntry entry : getContentTypeToLSPLaunches()) {
entry.appendPreferenceTo(builder);
builder.append(',');
}
if (builder.length() > 0) {
builder.deleteCharAt(builder.length() - 1);
}
this.preferenceStore.setValue(CONTENT_TYPE_TO_LSP_LAUNCH_PREF_KEY, builder.toString());
if (this.preferenceStore instanceof IPersistentPreferenceStore) {
try {
((IPersistentPreferenceStore) this.preferenceStore).save();
} catch (IOException e) {
LanguageServerPlugin.logError(e);
}
}
}
public List<LanguageServerDefinition> findProviderFor(final IContentType contentType) {
return connections.stream()
.filter(entry -> entry.getKey().equals(contentType))
.map(Entry::getValue)
.collect(Collectors.toList());
}
public void registerAssociation(@NonNull IContentType contentType, @NonNull ILaunchConfiguration launchConfig, @NonNull Set<String> launchMode) {
ContentTypeToLSPLaunchConfigEntry mapping = new ContentTypeToLSPLaunchConfigEntry(contentType, launchConfig, launchMode);
connections.add(mapping);
persistContentTypeToLaunchConfigurationMapping();
}
public void registerAssociation(@NonNull IContentType contentType, @NonNull LanguageServerDefinition serverDefinition) {
connections.add(new ContentTypeToLanguageServerDefinition(contentType, serverDefinition));
}
public void setAssociations(List<ContentTypeToLSPLaunchConfigEntry> wc) {
this.connections.removeIf(ContentTypeToLSPLaunchConfigEntry.class::isInstance);
this.connections.addAll(wc);
persistContentTypeToLaunchConfigurationMapping();
}
public List<ContentTypeToLSPLaunchConfigEntry> getContentTypeToLSPLaunches() {
return this.connections.stream().filter(ContentTypeToLSPLaunchConfigEntry.class::isInstance).map(ContentTypeToLSPLaunchConfigEntry.class::cast).collect(Collectors.toList());
}
public List<ContentTypeToLanguageServerDefinition> getContentTypeToLSPExtensions() {
return this.connections.stream().filter(mapping -> mapping.getValue() instanceof ExtensionLanguageServerDefinition).collect(Collectors.toList());
}
}