| /*=============================================================================# |
| # 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; |
| } |
| |
| } |