From bf091d960c12b1fd370bd2cb7e7ebce4305cb48f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Dawid=20Paku=C5=82a?= Date: Tue, 3 Oct 2023 21:04:24 +0200 Subject: [PATCH] Introduce universal configuration provider --- org.eclipse.lsp4e/META-INF/MANIFEST.MF | 6 +- org.eclipse.lsp4e/plugin.xml | 7 + org.eclipse.lsp4e/pom.xml | 2 +- org.eclipse.lsp4e/schema/languageServer.exsd | 7 + .../schema/languageServerConfiguration.exsd | 134 ++++++++++ .../org/eclipse/lsp4e/LanguageClientImpl.java | 10 +- .../eclipse/lsp4e/LanguageServerWrapper.java | 46 ++++ .../lsp4e/LanguageServersRegistry.java | 10 +- .../configuration/ConfigurationRegistry.java | 228 ++++++++++++++++++ .../EclipsePreferenceProvider.java | 89 +++++++ .../IConfigurationChangeListener.java | 17 ++ .../configuration/IConfigurationProvider.java | 28 +++ .../lsp4e/internal/SupportedFeatures.java | 5 + 13 files changed, 581 insertions(+), 8 deletions(-) create mode 100644 org.eclipse.lsp4e/schema/languageServerConfiguration.exsd create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/ConfigurationRegistry.java create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/EclipsePreferenceProvider.java create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationChangeListener.java create mode 100644 org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationProvider.java diff --git a/org.eclipse.lsp4e/META-INF/MANIFEST.MF b/org.eclipse.lsp4e/META-INF/MANIFEST.MF index c6016e2cd..ed2b9f95a 100644 --- a/org.eclipse.lsp4e/META-INF/MANIFEST.MF +++ b/org.eclipse.lsp4e/META-INF/MANIFEST.MF @@ -2,7 +2,7 @@ Manifest-Version: 1.0 Bundle-ManifestVersion: 2 Bundle-Name: Language Server Protocol client for Eclipse IDE (Incubation) Bundle-SymbolicName: org.eclipse.lsp4e;singleton:=true -Bundle-Version: 0.17.3.qualifier +Bundle-Version: 0.18.0.qualifier Bundle-RequiredExecutionEnvironment: JavaSE-17 Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0", org.eclipse.equinox.common;bundle-version="3.8.0", @@ -43,13 +43,15 @@ Require-Bundle: org.eclipse.core.runtime;bundle-version="3.12.0", com.google.guava;bundle-version="30.1.0", org.eclipse.e4.core.commands, org.eclipse.compare.core, - org.eclipse.compare + org.eclipse.compare, + org.eclipse.core.net Bundle-ClassPath: . Bundle-Localization: plugin Bundle-ActivationPolicy: lazy Bundle-Activator: org.eclipse.lsp4e.LanguageServerPlugin Export-Package: org.eclipse.lsp4e;x-internal:=true, org.eclipse.lsp4e.command;x-internal:=true, + org.eclipse.lsp4e.configuration;x-internal:=true, org.eclipse.lsp4e.format;x-internal:=true, org.eclipse.lsp4e.operations.codeactions;x-internal:=true, org.eclipse.lsp4e.operations.completion;x-internal:=true, diff --git a/org.eclipse.lsp4e/plugin.xml b/org.eclipse.lsp4e/plugin.xml index 612355b3d..f712e7e22 100644 --- a/org.eclipse.lsp4e/plugin.xml +++ b/org.eclipse.lsp4e/plugin.xml @@ -2,6 +2,7 @@ + + + + + diff --git a/org.eclipse.lsp4e/pom.xml b/org.eclipse.lsp4e/pom.xml index bdc6f0e58..4b42b420c 100644 --- a/org.eclipse.lsp4e/pom.xml +++ b/org.eclipse.lsp4e/pom.xml @@ -7,7 +7,7 @@ org.eclipse.lsp4e eclipse-plugin - 0.17.3-SNAPSHOT + 0.18.0-SNAPSHOT diff --git a/org.eclipse.lsp4e/schema/languageServer.exsd b/org.eclipse.lsp4e/schema/languageServer.exsd index ad3dfe7e8..4b7118494 100644 --- a/org.eclipse.lsp4e/schema/languageServer.exsd +++ b/org.eclipse.lsp4e/schema/languageServer.exsd @@ -151,6 +151,13 @@ If set to a number bigger than zero, the server will run until the timeout is re + + + + comma separated list of configuration prefix that will be send after initialization and after ConfigurationRegistry.notifyChange calls + + + diff --git a/org.eclipse.lsp4e/schema/languageServerConfiguration.exsd b/org.eclipse.lsp4e/schema/languageServerConfiguration.exsd new file mode 100644 index 000000000..6d797cdcd --- /dev/null +++ b/org.eclipse.lsp4e/schema/languageServerConfiguration.exsd @@ -0,0 +1,134 @@ + + + + + + + + + This extension point allows to define configuration sources. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Configuration provider report supported configuration keys and fetch them on demand + +To notify servers about change, implementors should call ConfigurationRegistry.notifyChange + + + + + + + + + + + + + + + + + + + + Allow map configuration supported by providers to they alias + + + + + + + Original settings paths already support by another provider + + + + + + + New settings paths + + + + + + + + + + + + [Enter the first release in which this extension point appears.] + + + + + + + + + [Enter extension point usage example here.] + + + + + + + + + [Enter API information here.] + + + + + + + + + [Enter information about supplied implementation of this extension point.] + + + + + diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageClientImpl.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageClientImpl.java index 59427d729..b821f934a 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageClientImpl.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageClientImpl.java @@ -26,11 +26,13 @@ import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.source.SourceViewer; +import org.eclipse.lsp4e.configuration.ConfigurationRegistry; import org.eclipse.lsp4e.progress.LSPProgressManager; import org.eclipse.lsp4e.ui.Messages; import org.eclipse.lsp4e.ui.UI; import org.eclipse.lsp4j.ApplyWorkspaceEditParams; import org.eclipse.lsp4j.ApplyWorkspaceEditResponse; +import org.eclipse.lsp4j.ConfigurationItem; import org.eclipse.lsp4j.ConfigurationParams; import org.eclipse.lsp4j.Location; import org.eclipse.lsp4j.MessageActionItem; @@ -75,8 +77,12 @@ protected final LanguageServer getLanguageServer() { public CompletableFuture> configuration(ConfigurationParams configurationParams) { // override as needed List list = new ArrayList<>(configurationParams.getItems().size()); - for (int i = 0; i < configurationParams.getItems().size(); i++) { - list.add(null); + for (ConfigurationItem item: configurationParams.getItems()) { + if (item.getScopeUri() == null) { + list.add(ConfigurationRegistry.getInstance().resolve(item.getSection())); + } else { + list.add(null); + } } return CompletableFuture.completedFuture(list); } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java index b1019d4cc..35cef9a1a 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServerWrapper.java @@ -66,6 +66,7 @@ import org.eclipse.jdt.annotation.Nullable; import org.eclipse.jface.text.IDocument; import org.eclipse.lsp4e.LanguageServersRegistry.LanguageServerDefinition; +import org.eclipse.lsp4e.configuration.ConfigurationRegistry; import org.eclipse.lsp4e.internal.FileBufferListenerAdapter; import org.eclipse.lsp4e.internal.SupportedFeatures; import org.eclipse.lsp4e.server.StreamConnectionProvider; @@ -74,6 +75,7 @@ import org.eclipse.lsp4j.ClientCapabilities; import org.eclipse.lsp4j.ClientInfo; import org.eclipse.lsp4j.CodeActionOptions; +import org.eclipse.lsp4j.DidChangeConfigurationParams; import org.eclipse.lsp4j.DidChangeWorkspaceFoldersParams; import org.eclipse.lsp4j.DocumentFormattingOptions; import org.eclipse.lsp4j.DocumentRangeFormattingOptions; @@ -161,6 +163,7 @@ public void dirtyStateChanged(IFileBuffer buffer, boolean isDirty) { private final Timer timer = new Timer("Stop Language Server Task Processor"); //$NON-NLS-1$ private TimerTask stopTimerTask; private AtomicBoolean stopping = new AtomicBoolean(false); + private String[] subscribedConfig; private final ExecutorService dispatcher; @@ -189,6 +192,7 @@ private LanguageServerWrapper(@Nullable IProject project, @NonNull LanguageServe this.initialPath = initialPath; this.serverDefinition = serverDefinition; this.connectedDocuments = new HashMap<>(); + this.subscribedConfig = serverDefinition.subcribedConfigurations; String projectName = (project != null && project.getName() != null && !serverDefinition.isSingleton) ? ("@" + project.getName()) : ""; //$NON-NLS-1$//$NON-NLS-2$ String dispatcherThreadNameFormat = "LS-" + serverDefinition.id + projectName + "#dispatcher"; //$NON-NLS-1$ //$NON-NLS-2$ this.dispatcher = Executors @@ -307,6 +311,10 @@ public synchronized void start() throws IOException { this.initiallySupportsWorkspaceFolders = supportsWorkspaceFolders(serverCapabilities); }).thenRun(() -> { this.languageServer.initialized(new InitializedParams()); + }).thenRun(() -> { + if (this.subscribedConfig != null && this.subscribedConfig.length > 0) { + sendConfiguration(); + } }).thenRun(() -> { final Map toReconnect = filesToReconnect; initializeFuture.thenRunAsync(() -> { @@ -718,6 +726,34 @@ public void sendNotification(@NonNull Consumer fn) { getInitializedServer().thenAcceptAsync(fn, this.dispatcher); } + public void configurationChanged(String[] paths) { + if (this.subscribedConfig == null) { + return; + } + if (subscribedConfig.length == 0) { + sendNotification(ls -> ls.getWorkspaceService().didChangeConfiguration(new DidChangeConfigurationParams())); + } else { + for (String prefix : subscribedConfig) { + for (String path : paths) { + if (path.startsWith(prefix)) { + sendConfiguration(); + return; + } + } + } + } + } + + private void sendConfiguration() + { + Map obj = new HashMap<>(); + for (String prefx : subscribedConfig) { + obj.put(prefx, ConfigurationRegistry.getInstance().resolve(prefx)); + } + + sendNotification(ls -> ls.getWorkspaceService().didChangeConfiguration(new DidChangeConfigurationParams(obj))); + } + /** * Runs a request on the language server * @@ -913,6 +949,16 @@ void registerCapability(RegistrationParams params) { serverCapabilities.setTypeHierarchyProvider(Boolean.TRUE); addRegistration(reg, () -> serverCapabilities.setTypeHierarchyProvider(typeHierarchyBeforeRegistration)); break; + + case "workspace/didChangeConfiguration": //$NON-NLS-1$ + synchronized(this) { + this.subscribedConfig = new String[0]; + addRegistration(reg, () -> { + subscribedConfig = null; + }); + } + + break; }}); } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServersRegistry.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServersRegistry.java index c77e92df5..6d5d3b667 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServersRegistry.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/LanguageServersRegistry.java @@ -89,6 +89,8 @@ public class LanguageServersRegistry { private static final String LABEL_ATTRIBUTE = "label"; //$NON-NLS-1$ private static final String ENABLED_WHEN_ATTRIBUTE = "enabledWhen"; //$NON-NLS-1$ private static final String ENABLED_WHEN_DESC = "description"; //$NON-NLS-1$ + private static final String WATCH_CONFIGURATION = "watchConfiguration"; //$NON-NLS-1$ + private static final String COMMA = ","; //$NON-NLS-1$ public abstract static class LanguageServerDefinition { public final @NonNull String id; @@ -96,13 +98,15 @@ public abstract static class LanguageServerDefinition { public final boolean isSingleton; public final int lastDocumentDisconnectedTimeout; public final @NonNull Map languageIdMappings; + public final String[] subcribedConfigurations; - LanguageServerDefinition(@NonNull String id, @NonNull String label, boolean isSingleton, int lastDocumentDisconnectedTimeout) { + LanguageServerDefinition(@NonNull String id, @NonNull String label, boolean isSingleton, int lastDocumentDisconnectedTimeout, String[] subscribedConfigurations) { this.id = id; this.label = label; this.isSingleton = isSingleton; this.lastDocumentDisconnectedTimeout = lastDocumentDisconnectedTimeout; this.languageIdMappings = new ConcurrentHashMap<>(); + this.subcribedConfigurations = subscribedConfigurations; } public void registerAssociation(@NonNull IContentType contentType, @NonNull String languageId) { @@ -159,7 +163,7 @@ private static int getLastDocumentDisconnectedTimeout(IConfigurationElement elem } public ExtensionLanguageServerDefinition(@NonNull IConfigurationElement element) { - super(element.getAttribute(ID_ATTRIBUTE), element.getAttribute(LABEL_ATTRIBUTE), getIsSingleton(element), getLastDocumentDisconnectedTimeout(element)); + super(element.getAttribute(ID_ATTRIBUTE), element.getAttribute(LABEL_ATTRIBUTE), getIsSingleton(element), getLastDocumentDisconnectedTimeout(element), element.getAttribute(WATCH_CONFIGURATION) == null ? null : element.getAttribute(WATCH_CONFIGURATION).split(COMMA)); this.extension = element; } @@ -232,7 +236,7 @@ static class LaunchConfigurationLanguageServerDefinition extends LanguageServerD public LaunchConfigurationLanguageServerDefinition(ILaunchConfiguration launchConfiguration, Set launchModes) { - super(launchConfiguration.getName(), launchConfiguration.getName(), DEFAULT_SINGLETON, DEFAULT_LAST_DOCUMENTED_DISCONNECTED_TIEMOUT); + super(launchConfiguration.getName(), launchConfiguration.getName(), DEFAULT_SINGLETON, DEFAULT_LAST_DOCUMENTED_DISCONNECTED_TIEMOUT, null); this.launchConfiguration = launchConfiguration; this.launchModes = launchModes; } diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/ConfigurationRegistry.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/ConfigurationRegistry.java new file mode 100644 index 000000000..7a94faf89 --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/ConfigurationRegistry.java @@ -0,0 +1,228 @@ +/******************************************************************************* + * Copyright (c) 2023 Dawid Pakuła and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dawid Pakuła - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.configuration; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Timer; +import java.util.TimerTask; +import java.util.TreeMap; + +import org.eclipse.core.runtime.CoreException; +import org.eclipse.core.runtime.IConfigurationElement; +import org.eclipse.core.runtime.Platform; +import org.eclipse.lsp4e.LanguageServerPlugin; +import org.eclipse.lsp4e.LanguageServiceAccessor; +import org.eclipse.ui.statushandlers.StatusManager; + +public class ConfigurationRegistry { + + private static final String EXTENSION_POINT_ID = LanguageServerPlugin.PLUGIN_ID + ".languageServerConfiguration"; //$NON-NLS-1$ + private static final String ALIAS_ELEMENT = "alias"; //$NON-NLS-1$ + private static final String PROVIDER_ELEMENT = "provider"; //$NON-NLS-1$ + + private static final String CLASS_ATTR = "class"; //$NON-NLS-1$ + + private static final String SOURCE_ATTR = "source"; //$NON-NLS-1$ + + private static final String TARGET_ATTR = "target"; //$NON-NLS-1$ + + private static final String DOT = "."; //$NON-NLS-1$ + + private static final String DOT_REGEX = "[.]"; //$NON-NLS-1$ + + private static final class LazyHolder { + static final ConfigurationRegistry INSTANCE = new ConfigurationRegistry(); + } + + public static ConfigurationRegistry getInstance() { + return LazyHolder.INSTANCE; + } + + private Map tree; + + private Throttler throtler; + + private Map aliases; + + private ConfigurationRegistry() { + initialize(); + } + + private class Throttler { + List paths = new ArrayList<>(); + Timer timer = new Timer(); + + public void queue(String path) { + synchronized(paths) { + paths.add(path); + } + timer.schedule(new TimerTask() { + + @Override + public void run() { + String[] list; + synchronized(paths) { + list = paths.toArray(new String[0]); + paths.clear(); + } + if (list.length != 0) { + settingsChanged(list); + } + + } + }, 100); + } + + } + + class AliasProvider implements IConfigurationProvider { + private String source; + private String target; + private IConfigurationProvider parentProvider; + + public AliasProvider(IConfigurationProvider parentProvider, String source, String target) { + this.source = source; + this.target = target; + this.parentProvider = parentProvider; + } + + @Override + public Object valueOf(String path) { + return parentProvider.valueOf(source); + } + + @Override + public String[] support() { + return new String[] { target }; + } + + } + + private void initialize() { + tree = new HashMap<>(); + throtler = new Throttler(); + IConfigurationElement[] extensions = Platform.getExtensionRegistry() + .getConfigurationElementsFor(EXTENSION_POINT_ID); + for (IConfigurationElement providerConfiguration : extensions) { + if (!providerConfiguration.getName().equals(PROVIDER_ELEMENT)) { + continue; + } + try { + IConfigurationProvider provider = (IConfigurationProvider) providerConfiguration + .createExecutableExtension(CLASS_ATTR); + for (String path : provider.support()) { + register(provider, path); + } + } catch (CoreException e) { + StatusManager.getManager().handle(e, LanguageServerPlugin.PLUGIN_ID); + } + } + aliases = new HashMap<>(); + for (IConfigurationElement alias : extensions) { + if (!alias.getName().equals(ALIAS_ELEMENT)) { + continue; + } + String source = alias.getAttribute(SOURCE_ATTR); + String target = alias.getAttribute(TARGET_ATTR); + Object resolve = resolveTree(source); + if (resolve instanceof IConfigurationProvider) { + register(new AliasProvider((IConfigurationProvider) resolve, source, target), + alias.getAttribute(TARGET_ATTR)); + aliases.put(source, target); + } else { + LanguageServerPlugin.logError("Invalid alias: " + target, null); //$NON-NLS-1$ + } + } + + } + + @SuppressWarnings("unchecked") + private Object resolveTree(String path) { + String[] parts = path.split(DOT_REGEX); + + Map local = tree; + for (String part : parts) { + if (!local.containsKey(part)) { + return null; + } + Object sub = local.get(part); + if (sub instanceof Map) { + local = (Map) sub; + } else if (sub instanceof IConfigurationProvider) { + return sub; + } + } + + return local; + + } + + @SuppressWarnings("unchecked") + private void register(IConfigurationProvider provider, String path) { + Map local = tree; + String[] parts = path.split(DOT_REGEX); + for (int index = 0; index < parts.length; index++) { + if (index == parts.length - 1) { + local.put(parts[index], provider); + } else { + if (!local.containsKey(parts[index])) { + local.put(parts[index], new HashMap<>()); + } + Object sub = local.get(parts[index]); + if (sub instanceof Map) { + local = (Map) sub; + } else { + LanguageServerPlugin.logError("Invalid configuration path: " + path, null); //$NON-NLS-1$ + break; + } + } + } + } + + void settingsChanged(String[] paths) { + LanguageServiceAccessor.getStartedWrappers(null, null, false).forEach(lsw -> { + lsw.configurationChanged(paths); + }); + + } + + public Object resolve(String path) { + return collect(resolveTree(path), path); + } + + private Object collect(Object resolved, String prefix) { + if (resolved instanceof IConfigurationProvider) { + return ((IConfigurationProvider) resolved).valueOf(prefix); + } else if (resolved instanceof Map) { + Map result = new TreeMap<>(); + for (Entry entry : ((Map) resolved).entrySet()) { + result.put(entry.getKey(), + collect(entry.getValue(), prefix.isEmpty() ? entry.getKey() : prefix + DOT + entry.getKey())); + } + + return result; + } + + return null; + } + + public void notifyChange(String path) { + this.throtler.queue(path); + if (aliases.containsKey(path)) { + this.throtler.queue(aliases.get(path)); + } + } +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/EclipsePreferenceProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/EclipsePreferenceProvider.java new file mode 100644 index 000000000..14889650f --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/EclipsePreferenceProvider.java @@ -0,0 +1,89 @@ +/******************************************************************************* + * Copyright (c) 2023 Dawid Pakuła and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dawid Pakuła - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.configuration; + +import java.util.HashMap; +import java.util.function.Function; + +import org.eclipse.core.runtime.Platform; +import org.eclipse.core.runtime.preferences.DefaultScope; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.IPreferenceChangeListener; +import org.eclipse.core.runtime.preferences.IEclipsePreferences.PreferenceChangeEvent; +import org.eclipse.core.runtime.preferences.IPreferencesService; +import org.eclipse.core.runtime.preferences.IScopeContext; +import org.eclipse.core.runtime.preferences.InstanceScope; + +public class EclipsePreferenceProvider implements IConfigurationProvider { + + private HashMap> supported; + + private IScopeContext[] sources; + private String rootPath; + + private IPreferencesService preferenceService; + + public EclipsePreferenceProvider(String rootPath) { + this.supported = new HashMap<>(); + this.sources = new IScopeContext[] { InstanceScope.INSTANCE, DefaultScope.INSTANCE }; + this.rootPath = rootPath; + this.preferenceService = Platform.getPreferencesService(); + + InstanceScope.INSTANCE.getNode(rootPath).addPreferenceChangeListener(new IPreferenceChangeListener() { + + @Override + public void preferenceChange(PreferenceChangeEvent event) { + if (supported.containsKey(event.getKey())) { + ConfigurationRegistry.getInstance().notifyChange(event.getKey()); + } + } + }); + } + + protected void add(String path) { + supported.put(path, Function.identity()); + } + + protected void addInt(String path) { + supported.put(path, Integer::valueOf); + } + + protected void addBool(String path) { + supported.put(path, Boolean::valueOf); + } + + protected void addFloat(String path) { + supported.put(path, Float::valueOf); + } + + protected void addLong(String path) { + supported.put(path, Long::valueOf); + } + + protected void add(String path, Function transformer) { + supported.put(path, transformer); + } + + @Override + public Object valueOf(String path) { + String val = preferenceService.getString(rootPath, path, null, sources); + if (val != null) { + return supported.getOrDefault(path, Function.identity()).apply(val); + } + return null; + } + + @Override + public String[] support() { + return supported.keySet().toArray(new String[0]); + } + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationChangeListener.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationChangeListener.java new file mode 100644 index 000000000..c95fcd041 --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationChangeListener.java @@ -0,0 +1,17 @@ +/******************************************************************************* + * Copyright (c) 2023 Dawid Pakuła and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dawid Pakuła - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.configuration; + +public interface IConfigurationChangeListener { + + void changed(String name); +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationProvider.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationProvider.java new file mode 100644 index 000000000..ddce6ba99 --- /dev/null +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/configuration/IConfigurationProvider.java @@ -0,0 +1,28 @@ +/******************************************************************************* + * Copyright (c) 2023 Dawid Pakuła and others. + * This program and the accompanying materials are made + * available under the terms of the Eclipse Public License 2.0 + * which is available at https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Dawid Pakuła - initial implementation + *******************************************************************************/ +package org.eclipse.lsp4e.configuration; + +public interface IConfigurationProvider { + + /** + * Fetch Value + */ + Object valueOf(String path); + + /** + * List of settings supported by this provider + * + * @return + */ + String[] support(); + +} diff --git a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java index 7b387d898..caf1faf01 100644 --- a/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java +++ b/org.eclipse.lsp4e/src/org/eclipse/lsp4e/internal/SupportedFeatures.java @@ -30,6 +30,7 @@ import org.eclipse.lsp4j.CompletionItemResolveSupportCapabilities; import org.eclipse.lsp4j.CompletionListCapabilities; import org.eclipse.lsp4j.DefinitionCapabilities; +import org.eclipse.lsp4j.DidChangeConfigurationCapabilities; import org.eclipse.lsp4j.DocumentHighlightCapabilities; import org.eclipse.lsp4j.DocumentLinkCapabilities; import org.eclipse.lsp4j.DocumentSymbolCapabilities; @@ -142,6 +143,10 @@ public class SupportedFeatures { workspaceClientCapabilities.setWorkspaceEdit(editCapabilities); CodeLensWorkspaceCapabilities codeLensWorkspaceCapabilities = new CodeLensWorkspaceCapabilities(true); workspaceClientCapabilities.setCodeLens(codeLensWorkspaceCapabilities); + + DidChangeConfigurationCapabilities didChangeConfigurationCapabilities = new DidChangeConfigurationCapabilities(Boolean.TRUE); + workspaceClientCapabilities.setDidChangeConfiguration(didChangeConfigurationCapabilities); + return workspaceClientCapabilities; }