blob: a6e61bfb76c753738f548392d81dcf0e841169fc [file] [log] [blame]
/*=============================================================================#
# Copyright (c) 2008, 2020 Stephan Wahlbrink 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, or the Apache License, Version 2.0
# which is available at https://www.apache.org/licenses/LICENSE-2.0.
#
# SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
#
# Contributors:
# Stephan Wahlbrink <sw@wahlbrink.eu> - initial API and implementation
#=============================================================================*/
package org.eclipse.statet.ltk.ui.sourceediting.assist;
import static org.eclipse.statet.jcommons.lang.ObjectUtils.nonNullAssert;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.osgi.framework.Bundle;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.core.runtime.IExtensionRegistry;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Platform;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.osgi.util.NLS;
import org.eclipse.ui.statushandlers.StatusManager;
import org.eclipse.statet.jcommons.collections.ImCollections;
import org.eclipse.statet.jcommons.collections.ImList;
import org.eclipse.statet.jcommons.lang.Disposable;
import org.eclipse.statet.jcommons.lang.NonNull;
import org.eclipse.statet.jcommons.lang.NonNullByDefault;
import org.eclipse.statet.jcommons.lang.Nullable;
import org.eclipse.statet.ecommons.preferences.PreferencesUtil;
import org.eclipse.statet.ecommons.preferences.SettingsChangeNotifier;
import org.eclipse.statet.ecommons.preferences.SettingsChangeNotifier.ManageListener;
import org.eclipse.statet.ecommons.preferences.core.Preference;
import org.eclipse.statet.ecommons.preferences.core.Preference.StringArrayPref;
import org.eclipse.statet.ecommons.preferences.core.Preference.StringSetPref;
import org.eclipse.statet.ecommons.preferences.core.PreferenceAccess;
import org.eclipse.statet.ecommons.preferences.core.util.PreferenceUtils;
import org.eclipse.statet.ecommons.ui.SharedUIResources;
import org.eclipse.statet.internal.ltk.ui.AdvancedExtensionsInternal;
/**
* Registry of contributions for {@link ContentAssistProcessor}s for a single content type.
*/
@NonNullByDefault
public class ContentAssistComputerRegistry implements ManageListener, Disposable {
public static String DEFAULT_DISABLED= "assist.default.disabled_categories.ids"; //$NON-NLS-1$
public static String CIRCLING_ORDERED= "assist.circling.ordered_categories.ids:enabled"; //$NON-NLS-1$
public static final Bundle getBundle(final IConfigurationElement element) {
final String namespace= element.getDeclaringExtension().getContributor().getName();
final Bundle bundle= Platform.getBundle(namespace);
return nonNullAssert(bundle);
}
protected static final Map<String, ContentAssistCategory> createCategoryByIdMap(final List<ContentAssistCategory> categories) {
final Map<String, ContentAssistCategory> map= new HashMap<>();
for (final ContentAssistCategory category : categories) {
map.put(category.getId(), category);
}
return map;
}
/**
* The description of an {@link ContentAssistComputer}
*/
final class ComputerDescriptor {
/** The identifier of the extension. */
private final String id;
private final Set<String> partitions;
/** The configuration element of this extension. */
private final IConfigurationElement configurationElement;
/** The computer, if instantiated, <code>null</code> otherwise. */
private @Nullable ContentAssistComputer computer;
/** Tells whether we tried to load the computer. */
private boolean triedLoadingComputer= false;
/**
* Creates a new descriptor with lazy loaded computer
*/
ComputerDescriptor(final String id, final Set<String> partitions, final IConfigurationElement configurationElement) {
this.id= id;
this.partitions= partitions;
this.configurationElement= configurationElement;
}
/**
* Returns the identifier of the described extension.
*
* @return Returns the id
*/
public String getId() {
return this.id;
}
/**
* Returns the partition types of the described extension.
*
* @return the set of partition types (element type: {@link String})
*/
public Set<String> getPartitions() {
return this.partitions;
}
/**
* Returns a cached instance of the computer
*/
public @Nullable ContentAssistComputer getComputer() {
if (this.computer == null && !this.triedLoadingComputer && this.configurationElement != null) {
this.triedLoadingComputer= true;
try {
this.computer= (ContentAssistComputer) this.configurationElement.createExecutableExtension(AdvancedExtensionsInternal.CONFIG_CLASS_ATTRIBUTE_NAME);
}
catch (final CoreException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, SharedUIResources.BUNDLE_ID, -1,
NLS.bind("Loading Content Assist Computer ''{0}'' failed (contributed by= '' {1}'').", //$NON-NLS-1$
this.id, this.configurationElement.getDeclaringExtension().getContributor().getName() ),
e ));
}
}
return this.computer;
}
}
class CategoryPreferences {
public final List<ContentAssistCategory> allCategories;
public final List<ContentAssistCategory> defaultEnabledCategories;
public final List<ContentAssistCategory> circlingOrderedCategories;
public final List<ContentAssistCategory> circlingOrderedEnabledCategories;
public CategoryPreferences(final List<ContentAssistCategory> categories) {
this.allCategories= categories;
this.defaultEnabledCategories= new ArrayList<>(categories.size());
this.circlingOrderedCategories= new ArrayList<>(categories.size());
this.circlingOrderedEnabledCategories= new ArrayList<>(categories.size());
}
}
private final String contentTypeId;
private final String qualifier;
private final StringSetPref prefDisabledCategoryIds;
private final StringArrayPref prefOrderedCategoryIds;
private @Nullable ImList<ComputerDescriptor> computers;
private @Nullable ImList<ContentAssistCategory> categories;
public ContentAssistComputerRegistry(final String contentTypeId, final String prefQualifier) {
this.contentTypeId= contentTypeId;
this.qualifier= prefQualifier;
this.prefDisabledCategoryIds= new StringSetPref(prefQualifier, DEFAULT_DISABLED);
this.prefOrderedCategoryIds= new StringArrayPref(prefQualifier, CIRCLING_ORDERED);
PreferencesUtil.getSettingsChangeNotifier().addManageListener(this);
}
@Override
public void dispose() {
final SettingsChangeNotifier notifier= PreferencesUtil.getSettingsChangeNotifier();
if (notifier != null) {
notifier.removeManageListener(this);
}
}
String getSettingsGroupId() {
return this.qualifier;
}
StringSetPref getPrefDefaultDisabledCategoryIds() {
return this.prefDisabledCategoryIds;
}
StringArrayPref getPrefCirclingOrderedCategoryIds() {
return this.prefOrderedCategoryIds;
}
@Override
public void beforeSettingsChangeNotification(final Set<String> groupIds) {
if (this.qualifier != null && groupIds.contains(this.qualifier)) {
synchronized (this) {
if (this.categories != null) {
this.categories= ImCollections.toList(
applyPreferences(PreferenceUtils.getInstancePrefs(), this.categories) );
}
}
}
}
@Override
public void afterSettingsChangeNotification(final Set<String> groupIds) {
}
private void loadExtensions() {
final ArrayList<IConfigurationElement> categoryConfigs= new ArrayList<>(); // categories of all content types!
final List<ComputerDescriptor> allComputers= new ArrayList<>();
final Map<String, List<ComputerDescriptor>> computersByCategoryId= new HashMap<>();
final IExtensionRegistry extensionRegistry= Platform.getExtensionRegistry();
final IConfigurationElement[] elements= extensionRegistry.getConfigurationElementsFor(
AdvancedExtensionsInternal.CONTENTASSIST_EXTENSIONPOINT_ID);
for (final IConfigurationElement element : elements) {
if (element.getName().equals(AdvancedExtensionsInternal.CONFIG_CATEGORY_ELEMENT_NAME)) {
categoryConfigs.add(element);
continue;
}
if (element.getName().equals(AdvancedExtensionsInternal.CONFIG_COMPUTER_ELEMENT_NAME)) {
// Create computer descriptor
String id= null;
try {
final String contentTypeId= AdvancedExtensionsInternal.getCheckedString(element, AdvancedExtensionsInternal.CONFIG_CONTENT_TYPE_ID_ATTRIBUTE_NAME);
if (!this.contentTypeId.equals(contentTypeId)) {
continue;
}
id= AdvancedExtensionsInternal.getCheckedString(element, AdvancedExtensionsInternal.CONFIG_ID_ATTRIBUTE_NAME).intern();
final String categoryId= AdvancedExtensionsInternal.getCheckedString(element, AdvancedExtensionsInternal.CONFIG_CATEGORY_ID_ATTRIBUTE_NAME);
final Set<String> partitions= new HashSet<>();
final IConfigurationElement[] partitionConfigs= element.getChildren(AdvancedExtensionsInternal.CONFIG_PARTITION_ELEMENT_NAME);
for (final IConfigurationElement partitionConfig : partitionConfigs) {
partitions.add(AdvancedExtensionsInternal.getCheckedString(partitionConfig, AdvancedExtensionsInternal.CONFIG_CONTENTTYPE_ID_ELEMENT_NAME).intern());
}
checkPartitions(partitions);
final ComputerDescriptor computer= new ComputerDescriptor(id, partitions, element);
allComputers.add(computer);
List<ComputerDescriptor> list= computersByCategoryId.get(categoryId);
if (list == null) {
list= new ArrayList<>(4);
computersByCategoryId.put(categoryId, list);
}
list.add(computer);
}
catch (final CoreException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, SharedUIResources.BUNDLE_ID, 0,
NLS.bind("Loading Completion Proposal Computer ''{0}'' failed (contributed by= ''{1}'')", //$NON-NLS-1$
(id != null) ? id : "", element.getDeclaringExtension().getContributor().getName() ), //$NON-NLS-1$
e ));
}
}
}
final ArrayList<ContentAssistCategory> categories= new ArrayList<>(8);
for (final IConfigurationElement catConfig : categoryConfigs) {
// Create category descriptor
String id= null;
try {
id= AdvancedExtensionsInternal.getCheckedString(catConfig, AdvancedExtensionsInternal.CONFIG_ID_ATTRIBUTE_NAME);
final List<ComputerDescriptor> descriptors= computersByCategoryId.get(id);
if (descriptors != null) {
final ImageDescriptor icon= AdvancedExtensionsInternal.getImageDescriptor(catConfig, AdvancedExtensionsInternal.CONFIG_ICON_ATTRIBUTE_NAME);
final String name= AdvancedExtensionsInternal.getCheckedString(catConfig, AdvancedExtensionsInternal.CONFIG_NAME_ATTRIBUTE_NAME);
final ContentAssistCategory cat= new ContentAssistCategory(id, name, icon, descriptors);
categories.add(cat);
}
}
catch (final CoreException e) {
StatusManager.getManager().handle(new Status(IStatus.ERROR, SharedUIResources.BUNDLE_ID, 0,
NLS.bind("Loading Completion Proposal Category ''{0}'' failed (contributed by= ''{1}'')", //$NON-NLS-1$
(id != null) ? id : "", catConfig.getDeclaringExtension().getContributor().getName() ), //$NON-NLS-1$
e ));
}
}
this.computers= ImCollections.toList(allComputers);
this.categories= ImCollections.toList(
applyPreferences(PreferenceUtils.getInstancePrefs(), categories) );
}
List<ContentAssistCategory> applyPreferences(final PreferenceAccess prefAccess,
final List<ContentAssistCategory> categories) {
final Set<String> disabledIds= prefAccess.getPreferenceValue(getPrefDefaultDisabledCategoryIds());
for (final ContentAssistCategory category : categories) {
final boolean enabled= disabledIds == null || !disabledIds.contains(category.getId());
category.isIncludedInDefault= enabled;
}
final Map<String, ContentAssistCategory> map= createCategoryByIdMap(categories);
final String[] orderPref= prefAccess.getPreferenceValue(getPrefCirclingOrderedCategoryIds());
final List<ContentAssistCategory> ordered= new ArrayList<>(categories.size());
for (final String value : orderPref) {
final String id;
final boolean enabled;
final int idx= value.lastIndexOf(':');
if (idx > 0) {
id= value.substring(0, idx);
enabled= Boolean.parseBoolean(value.substring(idx+1));
}
else { // fallback
id= value;
enabled= false;
}
final ContentAssistCategory category= map.remove(id);
if (category != null) {
ordered.add(category);
category.isEnabledAsSeparate= enabled;
}
}
for (final ContentAssistCategory category : map.values()) {
ordered.add(category);
category.isEnabledAsSeparate= false;
}
return ordered;
}
Map<Preference<?>, Object> createPreferences(final List<ContentAssistCategory> orderedCategories) {
final Set<String> disabledIds= new HashSet<>();
final String[] orderedPref= new @NonNull String[orderedCategories.size()];
for (int i= 0; i < orderedCategories.size(); i++) {
final ContentAssistCategory category= orderedCategories.get(i);
if (!category.isIncludedInDefault) {
disabledIds.add(category.getId());
}
orderedPref[i]= category.getId() +
(category.isEnabledAsSeparate ? ":true" : ":false"); //$NON-NLS-1$ //$NON-NLS-2$
}
final Map<Preference<?>, Object> prefMap= new HashMap<>();
prefMap.put(getPrefDefaultDisabledCategoryIds(), disabledIds);
prefMap.put(getPrefCirclingOrderedCategoryIds(), orderedPref);
return prefMap;
}
protected void checkPartitions(final Set<String> partitions) {
}
@SuppressWarnings("null")
public synchronized ImList<ContentAssistCategory> getCategories() {
ImList<ContentAssistCategory> categories= this.categories;
if (categories == null) {
loadExtensions();
categories= this.categories;
}
return categories;
}
public List<ContentAssistCategory> getCopyOfCategories() {
final ImList<ContentAssistCategory> categories= getCategories();
final List<ContentAssistCategory> copies= new ArrayList<>(categories.size());
for (final ContentAssistCategory category : categories) {
copies.add(new ContentAssistCategory(category));
}
return copies;
}
public ImList<ContentAssistCategory> getCategory(final String categoryId) {
final ImList<ContentAssistCategory> categories= getCategories();
for (final ContentAssistCategory category : categories) {
if (category.getId().equals(categoryId)) {
final ContentAssistCategory copy= new ContentAssistCategory(category);
copy.isIncludedInDefault= true;
return ImCollections.newList(copy);
}
}
return ImCollections.emptyList();
}
@SuppressWarnings("null")
private synchronized ImList<ComputerDescriptor> getComputers() {
ImList<ComputerDescriptor> computers= this.computers;
if (computers == null) {
loadExtensions();
computers= this.computers;
}
return computers;
}
public @Nullable ContentAssistComputer getComputer(final String id) {
final ImList<ComputerDescriptor> computers= getComputers();
for (final ComputerDescriptor descr : computers) {
if (descr.getId().equals(id)) {
return descr.getComputer();
}
}
return null;
}
}