| /******************************************************************************* |
| * Copyright (c) 2000, 2017 IBM Corporation and others. |
| * |
| * This program and the accompanying materials |
| * are made available under the terms of the Eclipse Public License 2.0 |
| * which accompanies this distribution, and is available at |
| * https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * Jan-Hendrik Diederich, Bredex GmbH - bug 201052 |
| * Carsten Pfeiffer, Gebit Solutions GmbH - bug 259536 |
| * Lars Vogel <Lars.Vogel@vogella.com> - Bug 472654 |
| * Mickael Istria, Red Hat Inc. - [91965] Add ct/editor mapping in user space |
| * Patrik Suzzi <psuzzi@gmail.com> - Bug 527069 |
| *******************************************************************************/ |
| package org.eclipse.ui.internal.registry; |
| |
| import com.ibm.icu.text.Collator; |
| import java.io.BufferedReader; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.io.Reader; |
| import java.io.StringReader; |
| import java.io.StringWriter; |
| import java.io.Writer; |
| import java.nio.charset.StandardCharsets; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Map; |
| import java.util.Map.Entry; |
| import java.util.StringTokenizer; |
| import org.eclipse.core.commands.common.EventManager; |
| import org.eclipse.core.runtime.IConfigurationElement; |
| import org.eclipse.core.runtime.IExtension; |
| import org.eclipse.core.runtime.IExtensionPoint; |
| import org.eclipse.core.runtime.IPath; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.SafeRunner; |
| import org.eclipse.core.runtime.content.IContentType; |
| import org.eclipse.core.runtime.content.IContentTypeManager; |
| import org.eclipse.core.runtime.dynamichelpers.ExtensionTracker; |
| import org.eclipse.core.runtime.dynamichelpers.IExtensionChangeHandler; |
| import org.eclipse.core.runtime.dynamichelpers.IExtensionTracker; |
| import org.eclipse.jface.dialogs.ErrorDialog; |
| import org.eclipse.jface.dialogs.MessageDialog; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.resource.ImageDescriptor; |
| import org.eclipse.jface.util.SafeRunnable; |
| import org.eclipse.swt.program.Program; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IEditorDescriptor; |
| import org.eclipse.ui.IEditorRegistry; |
| import org.eclipse.ui.IFileEditorMapping; |
| import org.eclipse.ui.IMemento; |
| import org.eclipse.ui.IPropertyListener; |
| import org.eclipse.ui.ISharedImages; |
| import org.eclipse.ui.PlatformUI; |
| import org.eclipse.ui.WorkbenchException; |
| import org.eclipse.ui.XMLMemento; |
| import org.eclipse.ui.activities.WorkbenchActivityHelper; |
| import org.eclipse.ui.internal.IPreferenceConstants; |
| import org.eclipse.ui.internal.IWorkbenchConstants; |
| import org.eclipse.ui.internal.IWorkbenchGraphicConstants; |
| import org.eclipse.ui.internal.WorkbenchImages; |
| import org.eclipse.ui.internal.WorkbenchMessages; |
| import org.eclipse.ui.internal.WorkbenchPlugin; |
| import org.eclipse.ui.internal.editorsupport.ComponentSupport; |
| import org.eclipse.ui.internal.misc.ExternalProgramImageDescriptor; |
| import org.eclipse.ui.internal.misc.ProgramImageDescriptor; |
| |
| /** |
| * Provides access to the collection of defined editors for resource types. |
| */ |
| public class EditorRegistry extends EventManager implements IEditorRegistry, IExtensionChangeHandler { |
| |
| private static final IEditorDescriptor[] EMPTY = new IEditorDescriptor[0]; |
| |
| class RelatedRegistry { |
| |
| /** |
| * Return the objects related to the type. |
| * |
| * @param type |
| * @return the objects related to the type |
| */ |
| public IEditorDescriptor[] getRelatedObjects(IContentType type) { |
| LinkedHashSet<IEditorDescriptor> editors = new LinkedHashSet<>(); |
| if (contentTypeToEditorMappingsFromPlugins.containsKey(type)) { |
| editors.addAll(Arrays.asList(contentTypeToEditorMappingsFromPlugins.get(type))); |
| } |
| if (contentTypeToEditorMappingsFromUser.containsKey(type)) { |
| editors.addAll(contentTypeToEditorMappingsFromUser.get(type)); |
| } |
| if (editors.isEmpty()) { |
| return EMPTY; |
| } |
| return (IEditorDescriptor[]) WorkbenchActivityHelper |
| .restrictArray(editors.toArray(new IEditorDescriptor[editors.size()])); |
| } |
| |
| /** |
| * Return the objects related to the filename |
| * |
| * @param fileName |
| * @return the objects related to the filename |
| */ |
| public IEditorDescriptor[] getRelatedObjects(String fileName) { |
| IFileEditorMapping mapping = getMappingFor(fileName); |
| if (mapping == null) { |
| return EMPTY; |
| } |
| |
| return (IEditorDescriptor[]) WorkbenchActivityHelper.restrictArray(mapping.getEditors()); |
| } |
| |
| } |
| |
| private Map<IContentType, IEditorDescriptor[]> contentTypeToEditorMappingsFromPlugins = new HashMap<>(); |
| private Map<IContentType, LinkedHashSet<IEditorDescriptor>> contentTypeToEditorMappingsFromUser = new HashMap<>(); |
| |
| /** |
| * Cached images - these include images from registered editors (via plugins) |
| * and others hence this table is not one to one with the mappings table. It is |
| * in fact a superset of the keys one would find in typeEditorMappings |
| */ |
| private Map<Object, ImageDescriptor> extensionImages = new HashMap<>(); |
| |
| /** |
| * Vector of EditorDescriptor - all the editors loaded from plugin files. The |
| * list is kept in order to be able to show in the editor selection dialog of |
| * the resource associations page. This list is sorted based on the human |
| * readable label of the editor descriptor. |
| * |
| * @see #comparer |
| */ |
| private List<IEditorDescriptor> sortedEditorsFromPlugins = new ArrayList<>(); |
| |
| // Map of EditorDescriptor - map editor id to editor. |
| private Map<String, IEditorDescriptor> mapIDtoEditor = initialIdToEditorMap(10); |
| |
| // Map of FileEditorMapping (extension to FileEditorMapping) |
| private EditorMap typeEditorMappings; |
| |
| /* |
| * Compares the labels from two IEditorDescriptor objects |
| */ |
| private static final Comparator<IEditorDescriptor> comparer = new Comparator<IEditorDescriptor>() { |
| private Collator collator = Collator.getInstance(); |
| |
| @Override |
| public int compare(IEditorDescriptor arg0, IEditorDescriptor arg1) { |
| String s1 = arg0.getLabel(); |
| String s2 = arg1.getLabel(); |
| return collator.compare(s1, s2); |
| } |
| }; |
| |
| private RelatedRegistry relatedRegistry; |
| |
| private final IContentTypeManager contentTypeManager; |
| |
| public static final String EMPTY_EDITOR_ID = "org.eclipse.ui.internal.emptyEditorTab"; //$NON-NLS-1$ |
| |
| /** |
| * Return an instance of the receiver. Adds listeners into the extension |
| * registry for dynamic UI purposes. |
| * |
| * @param contentTypeManager |
| */ |
| public EditorRegistry(IContentTypeManager contentTypeManager) { |
| super(); |
| this.contentTypeManager = contentTypeManager; |
| initializeFromStorage(); |
| IExtensionTracker tracker = PlatformUI.getWorkbench().getExtensionTracker(); |
| tracker.registerHandler(this, ExtensionTracker.createExtensionPointFilter(getExtensionPointFilter())); |
| relatedRegistry = new RelatedRegistry(); |
| contentTypeManager.addContentTypeChangeListener(event -> { |
| if (contentTypeManager.getContentType(event.getContentType().getId()) == null) { |
| contentTypeToEditorMappingsFromUser.remove(event.getContentType()); |
| saveAssociations(); |
| } |
| }); |
| } |
| |
| /** |
| * Add an editor for the given extensions with the specified (possibly null) |
| * extended type. The editor is being registered from a plugin |
| * |
| * @param editor The description of the editor (as obtained from the |
| * plugin file and built by the registry reader) |
| * @param extensions Collection of file extensions the editor applies to |
| * @param filenames Collection of filenames the editor applies to |
| * @param contentTypeVector |
| * @param bDefault Indicates whether the editor should be made the |
| * default editor and hence appear first inside a |
| * FileEditorMapping |
| * |
| * This method is not API and should not be called |
| * outside the workbench code. |
| */ |
| public void addEditorFromPlugin(EditorDescriptor editor, List<String> extensions, List<String> filenames, |
| List<String> contentTypeVector, boolean bDefault) { |
| |
| PlatformUI.getWorkbench().getExtensionTracker().registerObject( |
| editor.getConfigurationElement().getDeclaringExtension(), editor, IExtensionTracker.REF_WEAK); |
| // record it in our quick reference list |
| sortedEditorsFromPlugins.add(editor); |
| |
| // add it to the table of mappings |
| for (String fileExtension : extensions) { |
| if (fileExtension != null && fileExtension.length() > 0) { |
| FileEditorMapping mapping = getMappingFor("*." + fileExtension); //$NON-NLS-1$ |
| if (mapping == null) { // no mapping for that extension |
| mapping = new FileEditorMapping(fileExtension); |
| typeEditorMappings.putDefault(mappingKeyFor(mapping), mapping); |
| } |
| mapping.addEditor(editor); |
| if (bDefault) { |
| mapping.setDefaultEditor(editor); |
| } |
| } |
| } |
| |
| // add it to the table of mappings |
| for (String filename : filenames) { |
| if (filename != null && filename.length() > 0) { |
| FileEditorMapping mapping = getMappingFor(filename); |
| if (mapping == null) { // no mapping for that extension |
| String name; |
| String extension; |
| int index = filename.indexOf('.'); |
| if (index < 0) { |
| name = filename; |
| extension = ""; //$NON-NLS-1$ |
| } else { |
| name = filename.substring(0, index); |
| extension = filename.substring(index + 1); |
| } |
| mapping = new FileEditorMapping(name, extension); |
| typeEditorMappings.putDefault(mappingKeyFor(mapping), mapping); |
| } |
| mapping.addEditor(editor); |
| if (bDefault) { |
| mapping.setDefaultEditor(editor); |
| } |
| } |
| } |
| |
| for (String contentTypeId : contentTypeVector) { |
| if (contentTypeId != null && contentTypeId.length() > 0) { |
| IContentType contentType = contentTypeManager.getContentType(contentTypeId); |
| if (contentType != null) { |
| addContentTypeBindingFromPlugin(contentType, editor, bDefault); |
| } |
| } |
| } |
| |
| // Update editor map. |
| mapIDtoEditor.put(editor.getId(), editor); |
| } |
| |
| public void addContentTypeBindingFromPlugin(IContentType contentType, IEditorDescriptor editor, boolean bDefault) { |
| IEditorDescriptor[] editorArray = contentTypeToEditorMappingsFromPlugins.get(contentType); |
| if (editorArray == null) { |
| editorArray = new IEditorDescriptor[] { editor }; |
| contentTypeToEditorMappingsFromPlugins.put(contentType, editorArray); |
| } else { |
| IEditorDescriptor[] newArray = new IEditorDescriptor[editorArray.length + 1]; |
| if (bDefault) { // default editors go to the front of the line |
| newArray[0] = editor; |
| System.arraycopy(editorArray, 0, newArray, 1, editorArray.length); |
| } else { |
| newArray[editorArray.length] = editor; |
| System.arraycopy(editorArray, 0, newArray, 0, editorArray.length); |
| } |
| contentTypeToEditorMappingsFromPlugins.put(contentType, newArray); |
| } |
| } |
| |
| /** |
| * Add external editors to the editor mapping. |
| */ |
| private void addExternalEditorsToEditorMap() { |
| // Add registered editors (may include external editors). |
| for (FileEditorMapping map : typeEditorMappings.allMappings()) { |
| IEditorDescriptor[] descArray = map.getEditors(); |
| for (IEditorDescriptor desc : descArray) { |
| mapIDtoEditor.put(desc.getId(), desc); |
| } |
| } |
| // Add external editors from OS |
| for (IEditorDescriptor desc : getSortedEditorsFromOS()) { |
| mapIDtoEditor.put(desc.getId(), desc); |
| } |
| } |
| |
| @Override |
| public void addPropertyListener(IPropertyListener l) { |
| addListenerObject(l); |
| } |
| |
| @Override |
| public IEditorDescriptor findEditor(String id) { |
| IEditorDescriptor desc = mapIDtoEditor.get(id); |
| if (WorkbenchActivityHelper.restrictUseOf(desc)) { |
| return null; |
| } |
| return desc; |
| } |
| |
| /** |
| * Fires a property changed event to all registered listeners. |
| * |
| * @param type the type of event |
| * @see IEditorRegistry#PROP_CONTENTS |
| */ |
| private void firePropertyChange(final int type) { |
| for (Object listener : getListeners()) { |
| final IPropertyListener propertyListener = (IPropertyListener) listener; |
| SafeRunner.run(new SafeRunnable() { |
| @Override |
| public void run() { |
| propertyListener.propertyChanged(EditorRegistry.this, type); |
| } |
| }); |
| } |
| } |
| |
| @Override |
| public IEditorDescriptor getDefaultEditor() { |
| // the default editor will always be the system external editor |
| // this should never return null |
| return findEditor(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID); |
| } |
| |
| @Override |
| public IEditorDescriptor getDefaultEditor(String filename) { |
| IEditorDescriptor defaultEditor = getDefaultEditor(filename, guessAtContentType(filename)); |
| if (defaultEditor != null) { |
| return defaultEditor; |
| } |
| |
| IContentType[] contentTypes = contentTypeManager.findContentTypesFor(filename); |
| for (IContentType contentType : contentTypes) { |
| IEditorDescriptor editor = getDefaultEditor(filename, contentType); |
| if (editor != null) { |
| return editor; |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Return the (approximated) content type for a file with the given name. |
| * |
| * @param filename the filename |
| * @return the content type or <code>null</code> if it could not be determined |
| * @since 3.1 |
| */ |
| private IContentType guessAtContentType(String filename) { |
| return contentTypeManager.findContentTypeFor(filename); |
| } |
| |
| /** |
| * Returns the default file image descriptor. |
| * |
| * @return the image descriptor |
| */ |
| private ImageDescriptor getDefaultImage() { |
| // @issue what should be the default image? |
| return WorkbenchImages.getImageDescriptor(ISharedImages.IMG_OBJ_FILE); |
| } |
| |
| @Override |
| public IEditorDescriptor[] getEditors(String filename) { |
| return getEditors(filename, guessAtContentType(filename)); |
| } |
| |
| @Override |
| public IFileEditorMapping[] getFileEditorMappings() { |
| FileEditorMapping[] array = typeEditorMappings.allMappings(); |
| final Collator collator = Collator.getInstance(); |
| Arrays.sort(array, (o1, o2) -> { |
| String s1 = o1.getLabel(); |
| String s2 = o2.getLabel(); |
| return collator.compare(s1, s2); |
| }); |
| return array; |
| } |
| |
| @Override |
| public ImageDescriptor getImageDescriptor(String filename) { |
| return getImageDescriptor(filename, guessAtContentType(filename)); |
| } |
| |
| /** |
| * Find the file editor mapping for the file extension. Returns |
| * <code>null</code> if not found. |
| * |
| * @param ext the file extension |
| * @return the mapping, or <code>null</code> |
| */ |
| private FileEditorMapping getMappingFor(String ext) { |
| if (ext == null) { |
| return null; |
| } |
| String key = mappingKeyFor(ext); |
| return typeEditorMappings.get(key); |
| } |
| |
| /** |
| * Find the file editor mappings for the given filename. |
| * <p> |
| * Return an array of two FileEditorMapping items, where the first mapping is |
| * for the entire filename, and the second mapping is for the filename's |
| * extension only. These items can be null if no mapping exist on the filename |
| * and/or filename's extension. |
| * </p> |
| * |
| * @param filename the filename |
| * @return the mappings |
| */ |
| private FileEditorMapping[] getMappingForFilename(String filename) { |
| FileEditorMapping[] mapping = new FileEditorMapping[2]; |
| |
| // Lookup on entire filename |
| mapping[0] = getMappingFor(filename); |
| |
| // Lookup on filename's extension |
| int index = filename.lastIndexOf('.'); |
| if (index > -1) { |
| String extension = filename.substring(index); |
| mapping[1] = getMappingFor("*" + extension); //$NON-NLS-1$ |
| } |
| |
| return mapping; |
| } |
| |
| /** |
| * Return the editor descriptors pulled from the OS. |
| * <p> |
| * WARNING! The image described by each editor descriptor is *not* known by the |
| * workbench's graphic registry. Therefore clients must take care to ensure that |
| * if they access any of the images held by these editors that they also dispose |
| * them |
| * </p> |
| * |
| * @return the editor descriptors |
| */ |
| public IEditorDescriptor[] getSortedEditorsFromOS() { |
| List<IEditorDescriptor> externalEditors = new ArrayList<>(); |
| for (Program program : Program.getPrograms()) { |
| // 1FPLRL2: ITPUI:WINNT - NOTEPAD editor cannot be launched |
| // Some entries start with %SystemRoot% |
| // For such cases just use the file name as they are generally |
| // in directories which are on the path |
| /* |
| * if (fileName.charAt(0) == '%') { fileName = name + ".exe"; } |
| */ |
| |
| EditorDescriptor editor = new EditorDescriptor(); |
| editor.setOpenMode(EditorDescriptor.OPEN_EXTERNAL); |
| editor.setProgram(program); |
| |
| // determine the program icon this editor would need (do not let it |
| // be cached in the workbench registry) |
| ImageDescriptor desc = new ExternalProgramImageDescriptor(program); |
| editor.setImageDescriptor(desc); |
| externalEditors.add(editor); |
| } |
| |
| Object[] tempArray = sortEditors(externalEditors); |
| IEditorDescriptor[] array = new IEditorDescriptor[externalEditors.size()]; |
| for (int i = 0; i < tempArray.length; i++) { |
| array[i] = (IEditorDescriptor) tempArray[i]; |
| } |
| return array; |
| } |
| |
| /** |
| * Return the editors loaded from plugins. |
| * |
| * @return the sorted array of editors declared in plugins |
| */ |
| public IEditorDescriptor[] getSortedEditorsFromPlugins() { |
| // see #comparer |
| Collection<IEditorDescriptor> descs = WorkbenchActivityHelper.restrictCollection(sortedEditorsFromPlugins, |
| new ArrayList<IEditorDescriptor>()); |
| return descs.toArray(new IEditorDescriptor[descs.size()]); |
| } |
| |
| /** |
| * Answer an intial id to editor map. This will create a new map and populate it |
| * with the default system editors. |
| * |
| * @param initialSize the initial size of the map |
| * @return the new map |
| */ |
| private Map<String, IEditorDescriptor> initialIdToEditorMap(int initialSize) { |
| Map<String, IEditorDescriptor> map = new HashMap<>(initialSize); |
| addSystemEditors(map); |
| return map; |
| } |
| |
| /** |
| * Add the system editors to the provided map. This will always add an editor |
| * with an id of {@link #SYSTEM_EXTERNAL_EDITOR_ID} and may also add an editor |
| * with id of {@link #SYSTEM_INPLACE_EDITOR_ID} if the system configuration |
| * supports it. |
| * |
| * @param map the map to augment |
| */ |
| private void addSystemEditors(Map<String, IEditorDescriptor> map) { |
| // there will always be a system external editor descriptor |
| EditorDescriptor editor = new EditorDescriptor(); |
| editor.setID(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID); |
| editor.setName(WorkbenchMessages.SystemEditorDescription_name); |
| editor.setOpenMode(EditorDescriptor.OPEN_EXTERNAL); |
| // @issue we need a real icon for this editor? |
| map.put(IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID, editor); |
| |
| // there may be a system in-place editor if supported by platform |
| if (ComponentSupport.inPlaceEditorSupported()) { |
| editor = new EditorDescriptor(); |
| editor.setID(IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID); |
| editor.setName(WorkbenchMessages.SystemInPlaceDescription_name); |
| editor.setOpenMode(EditorDescriptor.OPEN_INPLACE); |
| // @issue we need a real icon for this editor? |
| map.put(IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID, editor); |
| } |
| |
| EditorDescriptor emptyEditorDescriptor = new EditorDescriptor(); |
| emptyEditorDescriptor.setID(EMPTY_EDITOR_ID); |
| emptyEditorDescriptor.setName("(Empty)"); //$NON-NLS-1$ |
| emptyEditorDescriptor |
| .setImageDescriptor(WorkbenchImages.getImageDescriptor(IWorkbenchGraphicConstants.IMG_OBJ_ELEMENT)); |
| map.put(EMPTY_EDITOR_ID, emptyEditorDescriptor); |
| } |
| |
| /** |
| * Initialize the registry state from plugin declarations and preference |
| * overrides. |
| */ |
| private void initializeFromStorage() { |
| typeEditorMappings = new EditorMap(); |
| extensionImages = new HashMap<>(); |
| |
| // Get editors from the registry |
| EditorRegistryReader registryReader = new EditorRegistryReader(); |
| registryReader.addEditors(this); |
| sortInternalEditors(); |
| rebuildInternalEditorMap(); |
| |
| IPreferenceStore store = PlatformUI.getPreferenceStore(); |
| String defaultEditors = store.getString(IPreferenceConstants.DEFAULT_EDITORS); |
| String chachedDefaultEditors = store.getString(IPreferenceConstants.DEFAULT_EDITORS_CACHE); |
| |
| // If defaults has changed load it afterwards so it overrides the users |
| // associations. |
| if (defaultEditors == null || defaultEditors.equals(chachedDefaultEditors)) { |
| setProductDefaults(defaultEditors); |
| loadAssociations(); // get saved earlier state |
| } else { |
| loadAssociations(); // get saved earlier state |
| setProductDefaults(defaultEditors); |
| store.putValue(IPreferenceConstants.DEFAULT_EDITORS_CACHE, defaultEditors); |
| } |
| addExternalEditorsToEditorMap(); |
| } |
| |
| /** |
| * Set the default editors according to the preference store which can be |
| * overwritten in the file properties.ini. In the form: |
| * <p> |
| * <code>ext1:id1;ext2:id2;...</code> |
| * </p> |
| * |
| * @param defaultEditors the default editors to set |
| */ |
| private void setProductDefaults(String defaultEditors) { |
| if (defaultEditors == null || defaultEditors.length() == 0) { |
| return; |
| } |
| |
| StringTokenizer extEditors = new StringTokenizer(defaultEditors, |
| Character.valueOf(IPreferenceConstants.SEPARATOR).toString()); |
| while (extEditors.hasMoreTokens()) { |
| String extEditor = extEditors.nextToken().trim(); |
| int index = extEditor.indexOf(':'); |
| if (extEditor.length() < 3 || index <= 0 || index >= (extEditor.length() - 1)) { |
| // Extension and id must have at least one char. |
| WorkbenchPlugin.log("Error setting default editor. Could not parse '" + extEditor //$NON-NLS-1$ |
| + "'. Default editors should be specified as '*.ext1:editorId1;*.ext2:editorId2'"); //$NON-NLS-1$ |
| return; |
| } |
| String ext = extEditor.substring(0, index).trim(); |
| String editorId = extEditor.substring(index + 1).trim(); |
| FileEditorMapping mapping = getMappingFor(ext); |
| if (mapping == null) { |
| WorkbenchPlugin.log("Error setting default editor. Could not find mapping for '" + ext + "'."); //$NON-NLS-1$ //$NON-NLS-2$ |
| continue; |
| } |
| IEditorDescriptor editor = findEditor(editorId); |
| if (editor == null) { |
| WorkbenchPlugin.log("Error setting default editor. Could not find editor: '" + editorId + "'."); //$NON-NLS-1$ //$NON-NLS-2$ |
| continue; |
| } |
| mapping.setDefaultEditor(editor); |
| } |
| } |
| |
| /** |
| * Read the editors defined in the preferences store. |
| * |
| * @param editorTable Editor table to store the editor definitions. |
| * @return true if the table is built succesfully. |
| */ |
| private boolean readEditors(Map<String, IEditorDescriptor> editorTable) { |
| // Get the workbench plugin's working directory |
| IPath workbenchStatePath = WorkbenchPlugin.getDefault().getDataLocation(); |
| if (workbenchStatePath == null) { |
| return false; |
| } |
| IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore(); |
| Reader reader = null; |
| FileInputStream stream = null; |
| try { |
| // Get the editors defined in the preferences store |
| String xmlString = store.getString(IPreferenceConstants.EDITORS); |
| if (xmlString == null || xmlString.length() == 0) { |
| stream = new FileInputStream( |
| workbenchStatePath.append(IWorkbenchConstants.EDITOR_FILE_NAME).toOSString()); |
| reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); |
| } else { |
| reader = new StringReader(xmlString); |
| } |
| XMLMemento memento = XMLMemento.createReadRoot(reader); |
| // Get the editors and validate each one |
| for (IMemento childMemento : memento.getChildren(IWorkbenchConstants.TAG_DESCRIPTOR)) { |
| EditorDescriptor editor = new EditorDescriptor(); |
| boolean valid = editor.loadValues(childMemento); |
| if (!valid) { |
| continue; |
| } |
| if (isSystem(editor.getId())) { |
| // bug 502514: check if there is internal editor |
| // descriptor (they are always recreated via |
| // addSystemEditors(Map<String, IEditorDescriptor>)) |
| lookupEditorFromTable(editorTable, editor); |
| continue; |
| } |
| if (editor.getPluginID() != null) { |
| // If the editor is from a plugin we use its ID to look it |
| // up in the mapping of editors we |
| // have obtained from plugins. This allows us to verify that |
| // the editor is still valid |
| // and allows us to get the editor description from the |
| // mapping table which has |
| // a valid config element field. |
| lookupEditorFromTable(editorTable, editor); |
| continue; |
| } |
| // This is either from a program or a user defined editor |
| ImageDescriptor descriptor; |
| if (editor.getProgram() == null) { |
| String fileName = editor.getFileName(); |
| if (fileName == null) { |
| String error = "Both editor program and path are null for descriptor id: "; //$NON-NLS-1$ |
| error += editor.getId() + " with name: " + editor.getLabel(); //$NON-NLS-1$ |
| WorkbenchPlugin.log(error, new IllegalStateException()); |
| continue; |
| } |
| descriptor = new ProgramImageDescriptor(fileName, 0); |
| } else { |
| descriptor = new ExternalProgramImageDescriptor(editor.getProgram()); |
| } |
| editor.setImageDescriptor(descriptor); |
| editorTable.put(editor.getId(), editor); |
| } |
| } catch (IOException e) { |
| // Ignore this as the workbench may not yet have saved any state |
| return false; |
| } catch (WorkbenchException e) { |
| ErrorDialog.openError((Shell) null, WorkbenchMessages.EditorRegistry_errorTitle, |
| WorkbenchMessages.EditorRegistry_errorMessage, e.getStatus()); |
| return false; |
| } finally { |
| try { |
| if (reader != null) { |
| reader.close(); |
| } else if (stream != null) |
| stream.close(); |
| } catch (IOException ex) { |
| WorkbenchPlugin.log("Error reading editors: Could not close steam", ex); //$NON-NLS-1$ |
| } |
| } |
| |
| return true; |
| } |
| |
| /** |
| * @param id descriptor id |
| * @return true if the id is one of the system internal id's: |
| * {@link IEditorRegistry#SYSTEM_EXTERNAL_EDITOR_ID} or |
| * {@link IEditorRegistry#SYSTEM_INPLACE_EDITOR_ID} |
| */ |
| private static boolean isSystem(String id) { |
| return IEditorRegistry.SYSTEM_EXTERNAL_EDITOR_ID.equals(id) |
| || IEditorRegistry.SYSTEM_INPLACE_EDITOR_ID.equals(id); |
| } |
| |
| private void lookupEditorFromTable(Map<String, IEditorDescriptor> editorTable, EditorDescriptor editor) { |
| IEditorDescriptor validEditorDescritor = mapIDtoEditor.get(editor.getId()); |
| if (validEditorDescritor != null) { |
| editorTable.put(validEditorDescritor.getId(), validEditorDescritor); |
| } |
| } |
| |
| /** |
| * Read the file types and associate them to their defined editor(s). |
| * |
| * @param editorTable The editor table containing the defined editors. |
| * @param reader Reader containing the preferences content for the |
| * resources. |
| * |
| * @throws WorkbenchException |
| */ |
| public void readResources(Map<String, IEditorDescriptor> editorTable, Reader reader) throws WorkbenchException { |
| XMLMemento memento = XMLMemento.createReadRoot(reader); |
| String versionString = memento.getString(IWorkbenchConstants.TAG_VERSION); |
| boolean versionIs31 = "3.1".equals(versionString); //$NON-NLS-1$ |
| |
| for (IMemento childMemento : memento.getChildren(IWorkbenchConstants.TAG_INFO)) { |
| List<IEditorDescriptor> editors = getEditorDescriptors( |
| childMemento.getChildren(IWorkbenchConstants.TAG_EDITOR), editorTable); |
| editors.forEach(editor -> { |
| if (!mapIDtoEditor.containsKey(editor.getId())) { |
| mapIDtoEditor.put(editor.getId(), editor); |
| } |
| }); |
| String contentTypeId = childMemento.getString(IWorkbenchConstants.TAG_CONTENT_TYPE); |
| if (contentTypeId != null) { |
| IContentType contentType = contentTypeManager.getContentType(contentTypeId); |
| if (contentType != null) { |
| contentTypeToEditorMappingsFromUser.put(contentType, new LinkedHashSet<>(editors)); |
| } |
| } else { |
| String name = childMemento.getString(IWorkbenchConstants.TAG_NAME); |
| if (name == null) { |
| name = "*"; //$NON-NLS-1$ |
| } |
| String extension = childMemento.getString(IWorkbenchConstants.TAG_EXTENSION); |
| String key = name; |
| if (extension != null && extension.length() > 0) { |
| key = key + "." + extension; //$NON-NLS-1$ |
| } |
| FileEditorMapping mapping = getMappingFor(key); |
| if (mapping == null) { |
| mapping = new FileEditorMapping(name, extension); |
| } |
| |
| List<IEditorDescriptor> deletedEditors = getEditorDescriptors( |
| childMemento.getChildren(IWorkbenchConstants.TAG_DELETED_EDITOR), editorTable); |
| |
| List<IEditorDescriptor> defaultEditors = null; |
| if (versionIs31) { // parse the new format |
| defaultEditors = getEditorDescriptors( |
| childMemento.getChildren(IWorkbenchConstants.TAG_DEFAULT_EDITOR), editorTable); |
| } else { // guess at pre 3.1 format defaults |
| defaultEditors = new ArrayList<>( |
| (editors.isEmpty() ? 0 : 1) + mapping.getDeclaredDefaultEditors().length); |
| if (!editors.isEmpty()) { |
| IEditorDescriptor editor = editors.get(0); |
| defaultEditors.add(editor); |
| } |
| defaultEditors.addAll(Arrays.asList(mapping.getDeclaredDefaultEditors())); |
| } |
| |
| // Add any new editors that have already been read from the registry |
| // which were not deleted. |
| for (IEditorDescriptor descriptor : mapping.getEditors()) { |
| if (descriptor != null && !contains(editors, descriptor) && !deletedEditors.contains(descriptor)) { |
| editors.add(descriptor); |
| } |
| } |
| // Map the editor(s) to the file type |
| mapping.setEditorsList(editors); |
| mapping.setDeletedEditorsList(deletedEditors); |
| mapping.setDefaultEditors(defaultEditors); |
| typeEditorMappings.put(mappingKeyFor(mapping), mapping); |
| } |
| } |
| } |
| |
| /** |
| * @param children |
| * @param editorTable |
| * @return |
| */ |
| private List<IEditorDescriptor> getEditorDescriptors(IMemento[] children, |
| Map<String, IEditorDescriptor> editorTable) { |
| if (children == null || children.length == 0) { |
| return new ArrayList<>(0); // need a mutable list |
| } |
| List<IEditorDescriptor> res = new ArrayList<>(children.length); |
| for (IMemento child : children) { |
| String editorId = child.getString(IWorkbenchConstants.TAG_ID); |
| if (editorId != null && editorTable.containsKey(editorId)) { |
| res.add(editorTable.get(editorId)); |
| } |
| } |
| return res; |
| } |
| |
| /** |
| * Determine if the editors list contains the editor descriptor. |
| * |
| * @param editors The list of editors |
| * @param editorDescriptor The editor descriptor |
| * @return <code>true</code> if the editors list contains the editor descriptor |
| */ |
| private boolean contains(List<IEditorDescriptor> editors, IEditorDescriptor editorDescriptor) { |
| for (IEditorDescriptor currentEditorDescriptor : editors) { |
| if (currentEditorDescriptor.getId().equals(editorDescriptor.getId())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Creates the reader for the resources preferences defined in the preference |
| * store. |
| * |
| * @param editorTable The editor table containing the defined editors. |
| * @return true if the resources are read succesfully. |
| */ |
| private boolean readResources(Map<String, IEditorDescriptor> editorTable) { |
| // Get the workbench plugin's working directory |
| IPath workbenchStatePath = WorkbenchPlugin.getDefault().getDataLocation(); |
| // XXX: nobody cares about this return value |
| if (workbenchStatePath == null) { |
| return false; |
| } |
| IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore(); |
| Reader reader = null; |
| FileInputStream stream = null; |
| try { |
| // Get the resource types |
| String xmlString = store.getString(IPreferenceConstants.RESOURCES); |
| if (xmlString == null || xmlString.length() == 0) { |
| stream = new FileInputStream( |
| workbenchStatePath.append(IWorkbenchConstants.RESOURCE_TYPE_FILE_NAME).toOSString()); |
| reader = new BufferedReader(new InputStreamReader(stream, StandardCharsets.UTF_8)); |
| } else { |
| reader = new StringReader(xmlString); |
| } |
| // Read the defined resources into the table |
| readResources(editorTable, reader); |
| } catch (IOException e) { |
| MessageDialog.openError((Shell) null, WorkbenchMessages.EditorRegistry_errorTitle, |
| WorkbenchMessages.EditorRegistry_errorMessage); |
| return false; |
| } catch (WorkbenchException e) { |
| ErrorDialog.openError((Shell) null, WorkbenchMessages.EditorRegistry_errorTitle, |
| WorkbenchMessages.EditorRegistry_errorMessage, e.getStatus()); |
| return false; |
| } finally { |
| try { |
| if (reader != null) { |
| reader.close(); |
| } else if (stream != null) { |
| stream.close(); |
| } |
| } catch (IOException ex) { |
| WorkbenchPlugin.log("Error reading resources: Could not close steam", ex); //$NON-NLS-1$ |
| } |
| } |
| return true; |
| |
| } |
| |
| /** |
| * Load the serialized resource associations Return true if the operation was |
| * successful, false otherwise |
| */ |
| private boolean loadAssociations() { |
| Map<String, IEditorDescriptor> editorTable = new HashMap<>(); |
| if (!readEditors(editorTable)) { |
| return false; |
| } |
| return readResources(editorTable); |
| } |
| |
| /** |
| * Return a friendly version of the given key suitable for use in the editor |
| * map. |
| */ |
| private String mappingKeyFor(String type) { |
| // keep everything lower case for case-sensitive platforms |
| return type.toLowerCase(); |
| } |
| |
| /** |
| * Return a key that combines the file's name and extension of the given mapping |
| * |
| * @param mapping the mapping to generate a key for |
| */ |
| private String mappingKeyFor(FileEditorMapping mapping) { |
| return mappingKeyFor( |
| mapping.getName() + (mapping.getExtension().length() == 0 ? "" : "." + mapping.getExtension())); //$NON-NLS-1$ //$NON-NLS-2$ |
| } |
| |
| /** |
| * Rebuild the editor map |
| */ |
| private void rebuildEditorMap() { |
| rebuildInternalEditorMap(); |
| addExternalEditorsToEditorMap(); |
| } |
| |
| /** |
| * Rebuild the internal editor mapping. |
| */ |
| private void rebuildInternalEditorMap() { |
| // Allocate a new map. |
| mapIDtoEditor = initialIdToEditorMap(mapIDtoEditor.size()); |
| |
| // Add plugin editors. |
| for (IEditorDescriptor desc : sortedEditorsFromPlugins) { |
| mapIDtoEditor.put(desc.getId(), desc); |
| } |
| } |
| |
| @Override |
| public void removePropertyListener(IPropertyListener l) { |
| removeListenerObject(l); |
| } |
| |
| /** |
| * Save the registry to the filesystem by serializing the current resource |
| * associations. |
| */ |
| public void saveAssociations() { |
| // Save the resource type descriptions |
| LinkedHashSet<IEditorDescriptor> editors = new LinkedHashSet<>(); |
| IPreferenceStore store = WorkbenchPlugin.getDefault().getPreferenceStore(); |
| |
| XMLMemento memento = XMLMemento.createWriteRoot(IWorkbenchConstants.TAG_EDITORS); |
| memento.putString(IWorkbenchConstants.TAG_VERSION, "3.1"); //$NON-NLS-1$ |
| for (FileEditorMapping fileEditorMapping : typeEditorMappings.userMappings()) { |
| IMemento editorMemento = memento.createChild(IWorkbenchConstants.TAG_INFO); |
| editorMemento.putString(IWorkbenchConstants.TAG_NAME, fileEditorMapping.getName()); |
| editorMemento.putString(IWorkbenchConstants.TAG_EXTENSION, fileEditorMapping.getExtension()); |
| IEditorDescriptor[] editorArray = fileEditorMapping.getEditors(); |
| for (IEditorDescriptor editor : editorArray) { |
| if (editor == null) { |
| continue; |
| } |
| editors.add(editor); |
| IMemento idMemento = editorMemento.createChild(IWorkbenchConstants.TAG_EDITOR); |
| idMemento.putString(IWorkbenchConstants.TAG_ID, editor.getId()); |
| } |
| editorArray = fileEditorMapping.getDeletedEditors(); |
| for (IEditorDescriptor editor : editorArray) { |
| if (editor == null) { |
| continue; |
| } |
| editors.add(editor); |
| IMemento idMemento = editorMemento.createChild(IWorkbenchConstants.TAG_DELETED_EDITOR); |
| idMemento.putString(IWorkbenchConstants.TAG_ID, editor.getId()); |
| } |
| editorArray = fileEditorMapping.getDeclaredDefaultEditors(); |
| for (IEditorDescriptor editor : editorArray) { |
| if (editor == null) { |
| continue; |
| } |
| editors.add(editor); |
| IMemento idMemento = editorMemento.createChild(IWorkbenchConstants.TAG_DEFAULT_EDITOR); |
| idMemento.putString(IWorkbenchConstants.TAG_ID, editor.getId()); |
| } |
| } |
| for (Entry<IContentType, LinkedHashSet<IEditorDescriptor>> mapping : contentTypeToEditorMappingsFromUser |
| .entrySet()) { |
| IMemento editorMemento = memento.createChild(IWorkbenchConstants.TAG_INFO); |
| editorMemento.putString(IWorkbenchConstants.TAG_CONTENT_TYPE, mapping.getKey().getId()); |
| for (IEditorDescriptor editor : mapping.getValue()) { |
| if (editor == null) { |
| continue; |
| } |
| editors.add(editor); |
| IMemento idMemento = editorMemento.createChild(IWorkbenchConstants.TAG_EDITOR); |
| idMemento.putString(IWorkbenchConstants.TAG_ID, editor.getId()); |
| } |
| } |
| try (Writer writer = new StringWriter()) { |
| memento.save(writer); |
| writer.close(); |
| store.setValue(IPreferenceConstants.RESOURCES, writer.toString()); |
| } catch (IOException e) { |
| MessageDialog.openError((Shell) null, "Saving Problems", //$NON-NLS-1$ |
| "Unable to save resource associations."); //$NON-NLS-1$ |
| return; |
| } |
| |
| memento = XMLMemento.createWriteRoot(IWorkbenchConstants.TAG_EDITORS); |
| for (IEditorDescriptor editor : editors) { |
| if (isSystem(editor.getId())) { |
| // bug 502514: don't persist internal editor descriptors, |
| // they are always recreated via addSystemEditors(Map<String, |
| // IEditorDescriptor>) |
| continue; |
| } |
| IMemento editorMemento = memento.createChild(IWorkbenchConstants.TAG_DESCRIPTOR); |
| ((EditorDescriptor) editor).saveValues(editorMemento); |
| } |
| try (Writer writer = new StringWriter()) { |
| memento.save(writer); |
| writer.close(); |
| store.setValue(IPreferenceConstants.EDITORS, writer.toString()); |
| } catch (IOException e) { |
| MessageDialog.openError((Shell) null, "Error", "Unable to save resource associations."); //$NON-NLS-1$ //$NON-NLS-2$ |
| return; |
| } |
| } |
| |
| /** |
| * Set the collection of FileEditorMappings. The given collection is converted |
| * into the internal hash table for faster lookup Each mapping goes from an |
| * extension to the collection of editors that work on it. This operation will |
| * rebuild the internal editor mappings. |
| * |
| * @param newResourceTypes te new file editor mappings. |
| */ |
| public void setFileEditorMappings(FileEditorMapping[] newResourceTypes) { |
| typeEditorMappings = new EditorMap(); |
| for (FileEditorMapping mapping : newResourceTypes) { |
| typeEditorMappings.put(mappingKeyFor(mapping), mapping); |
| } |
| extensionImages = new HashMap<>(); |
| rebuildEditorMap(); |
| firePropertyChange(PROP_CONTENTS); |
| } |
| |
| @Override |
| public void setDefaultEditor(String fileName, String editorId) { |
| IEditorDescriptor desc = findEditor(editorId); |
| setDefaultEditor(fileName, desc); |
| } |
| |
| public void setDefaultEditor(String fileName, IEditorDescriptor desc) { |
| FileEditorMapping[] mapping = getMappingForFilename(fileName); |
| if (mapping[0] != null) { |
| mapping[0].setDefaultEditor(desc); |
| } |
| if (mapping[1] != null) { |
| mapping[1].setDefaultEditor(desc); |
| } |
| } |
| |
| /** |
| * Alphabetically sort the internal editors. |
| * |
| * @see #comparer |
| */ |
| private IEditorDescriptor[] sortEditors(List<IEditorDescriptor> unsortedList) { |
| IEditorDescriptor[] array = new IEditorDescriptor[unsortedList.size()]; |
| unsortedList.toArray(array); |
| |
| Collections.sort(Arrays.asList(array), comparer); |
| return array; |
| } |
| |
| /** |
| * Alphabetically sort the internal editors. |
| * |
| * @see #comparer |
| */ |
| private void sortInternalEditors() { |
| IEditorDescriptor[] array = sortEditors(sortedEditorsFromPlugins); |
| sortedEditorsFromPlugins = new ArrayList<>(); |
| for (IEditorDescriptor element : array) { |
| sortedEditorsFromPlugins.add(element); |
| } |
| } |
| |
| /** |
| * Map of FileEditorMapping (extension to FileEditorMapping) Uses two |
| * java.util.HashMap: one keeps the default which are set by the plugins and the |
| * other keeps the changes made by the user through the preference page. |
| */ |
| private static class EditorMap { |
| HashMap<String, FileEditorMapping> defaultMap = new HashMap<>(); |
| |
| HashMap<String, FileEditorMapping> map = new HashMap<>(); |
| |
| /** |
| * Put a default mapping into the editor map. |
| * |
| * @param key the key to set |
| * @param value the value to associate |
| */ |
| public void putDefault(String key, FileEditorMapping value) { |
| defaultMap.put(key, value); |
| } |
| |
| /** |
| * Put a mapping into the user editor map. |
| * |
| * @param key the key to set |
| * @param value the value to associate |
| */ |
| public void put(String key, FileEditorMapping value) { |
| Object result = defaultMap.get(key); |
| if (value.equals(result)) { |
| map.remove(key); |
| } else { |
| map.put(key, value); |
| } |
| } |
| |
| /** |
| * Return the mapping associated to the key. First searches user map, and then |
| * falls back to the default map if there is no match. May return |
| * <code>null</code> |
| * |
| * @param key the key to search for |
| * @return the mapping associated to the key or <code>null</code> |
| */ |
| public FileEditorMapping get(String key) { |
| Object result = map.get(key); |
| if (result == null) { |
| result = defaultMap.get(key); |
| } |
| return (FileEditorMapping) result; |
| } |
| |
| /** |
| * Return all mappings. This will return default mappings overlayed with user |
| * mappings. |
| * |
| * @return the mappings |
| */ |
| public FileEditorMapping[] allMappings() { |
| @SuppressWarnings("unchecked") |
| HashMap<String, FileEditorMapping> merge = (HashMap<String, FileEditorMapping>) defaultMap.clone(); |
| merge.putAll(map); |
| Collection<FileEditorMapping> values = merge.values(); |
| FileEditorMapping result[] = new FileEditorMapping[values.size()]; |
| return values.toArray(result); |
| } |
| |
| /** |
| * Return all user mappings. |
| * |
| * @return the mappings |
| */ |
| public FileEditorMapping[] userMappings() { |
| Collection<FileEditorMapping> values = map.values(); |
| FileEditorMapping result[] = new FileEditorMapping[values.size()]; |
| return values.toArray(result); |
| } |
| } |
| |
| @Override |
| public boolean isSystemInPlaceEditorAvailable(String filename) { |
| return ComponentSupport.inPlaceEditorAvailable(filename); |
| } |
| |
| @Override |
| public boolean isSystemExternalEditorAvailable(String filename) { |
| int nDot = filename.lastIndexOf('.'); |
| if (nDot >= 0) { |
| String strName = filename.substring(nDot); |
| return Program.findProgram(strName) != null; |
| } |
| return false; |
| } |
| |
| @Override |
| public ImageDescriptor getSystemExternalEditorImageDescriptor(String filename) { |
| Program externalProgram = null; |
| int extensionIndex = filename.lastIndexOf('.'); |
| if (extensionIndex >= 0) { |
| externalProgram = Program.findProgram(filename.substring(extensionIndex)); |
| } |
| if (externalProgram == null) { |
| return null; |
| } |
| |
| return new ExternalProgramImageDescriptor(externalProgram); |
| } |
| |
| /** |
| * Removes the entry with the value of the editor descriptor from the given map. |
| * If the descriptor is the last descriptor in a given FileEditorMapping then |
| * the mapping is removed from the map. |
| * |
| * @param map the map to search |
| * @param desc the descriptor value to remove |
| */ |
| private void removeEditorFromMapping(HashMap<String, FileEditorMapping> map, IEditorDescriptor desc) { |
| Iterator<FileEditorMapping> iter = map.values().iterator(); |
| while (iter.hasNext()) { |
| FileEditorMapping mapping = iter.next(); |
| for (IEditorDescriptor editor : mapping.getUnfilteredEditors()) { |
| if (editor == desc) { |
| mapping.removeEditor(editor); |
| break; |
| } |
| } |
| IEditorDescriptor[] editors = mapping.getUnfilteredEditors(); |
| if (editors.length == 0) { |
| iter.remove(); |
| } |
| } |
| } |
| |
| @Override |
| public void removeExtension(IExtension source, Object[] objects) { |
| for (Object object : objects) { |
| if (object instanceof IEditorDescriptor) { |
| IEditorDescriptor desc = (IEditorDescriptor) object; |
| |
| sortedEditorsFromPlugins.remove(desc); |
| mapIDtoEditor.values().remove(desc); |
| removeEditorFromMapping(typeEditorMappings.defaultMap, desc); |
| removeEditorFromMapping(typeEditorMappings.map, desc); |
| removeEditorFromContentTypeMappings(contentTypeToEditorMappingsFromPlugins, desc); |
| } |
| |
| } |
| } |
| |
| /** |
| * Removes all occurrences of the given editor descriptor from the map of |
| * content types. If the descriptor was the only editor, the whole content type |
| * is removed from the map. |
| */ |
| private void removeEditorFromContentTypeMappings(Map<IContentType, IEditorDescriptor[]> map, |
| IEditorDescriptor desc) { |
| for (Iterator<Entry<IContentType, IEditorDescriptor[]>> iter = map.entrySet().iterator(); iter.hasNext();) { |
| Entry<IContentType, IEditorDescriptor[]> entry = iter.next(); |
| IEditorDescriptor[] descriptors = entry.getValue(); |
| IEditorDescriptor[] newDescriptors = removeDescriptor(descriptors, desc); |
| if (descriptors != newDescriptors) { |
| if (newDescriptors == null) { |
| iter.remove(); |
| } else { |
| entry.setValue(newDescriptors); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Checks the given IEditorDescriptor for an occurrence of the given descriptor |
| * and returns an array not containing this descriptor. If the result would then |
| * be an empty array, <code>null</code> is returned. If the descriptor is not |
| * contained at all in the given array, it is returned as is. |
| */ |
| private IEditorDescriptor[] removeDescriptor(IEditorDescriptor[] descriptors, IEditorDescriptor desc) { |
| for (int i = 0; i < descriptors.length; i++) { |
| if (descriptors[i] == desc) { |
| // remove the whole mapping |
| if (descriptors.length == 1) { |
| return null; |
| } |
| |
| IEditorDescriptor[] newDescriptors = new IEditorDescriptor[descriptors.length - 1]; |
| if (i == 0) { |
| System.arraycopy(descriptors, 1, newDescriptors, 0, newDescriptors.length); |
| } else { |
| System.arraycopy(descriptors, 0, newDescriptors, 0, i); |
| if (i < newDescriptors.length) { |
| System.arraycopy(descriptors, i + 1, newDescriptors, i, newDescriptors.length - i); |
| } |
| } |
| return newDescriptors; |
| } |
| } |
| |
| return descriptors; |
| } |
| |
| @Override |
| public void addExtension(IExtensionTracker tracker, IExtension extension) { |
| EditorRegistryReader eReader = new EditorRegistryReader(); |
| IConfigurationElement[] elements = extension.getConfigurationElements(); |
| for (IConfigurationElement element : elements) { |
| String id = element.getAttribute(IWorkbenchConstants.TAG_ID); |
| if (id != null && findEditor(id) != null) { |
| continue; |
| } |
| eReader.readElement(this, element); |
| } |
| } |
| |
| private IExtensionPoint getExtensionPointFilter() { |
| return Platform.getExtensionRegistry().getExtensionPoint(PlatformUI.PLUGIN_ID, |
| IWorkbenchRegistryConstants.PL_EDITOR); |
| } |
| |
| @Override |
| public IEditorDescriptor getDefaultEditor(String fileName, IContentType contentType) { |
| return getEditorForContentType(fileName, contentType); |
| } |
| |
| /** |
| * Return the editor for a file with a given content type. |
| * |
| * @param filename the file name |
| * @param contentType the content type |
| * @return the editor for a file with a given content type |
| * @since 3.1 |
| */ |
| private IEditorDescriptor getEditorForContentType(String filename, IContentType contentType) { |
| IEditorDescriptor desc = null; |
| Object[] contentTypeResults = findRelatedObjects(contentType, filename, relatedRegistry); |
| if (contentTypeResults != null && contentTypeResults.length > 0) { |
| desc = (IEditorDescriptor) contentTypeResults[0]; |
| } |
| return desc; |
| } |
| |
| @Override |
| public IEditorDescriptor[] getEditors(String fileName, IContentType contentType) { |
| return findRelatedObjects(contentType, fileName, relatedRegistry); |
| } |
| |
| @Override |
| public ImageDescriptor getImageDescriptor(String filename, IContentType contentType) { |
| if (filename == null) { |
| return getDefaultImage(); |
| } |
| |
| if (contentType != null) { |
| IEditorDescriptor desc = getEditorForContentType(filename, contentType); |
| if (desc != null) { |
| ImageDescriptor anImage = extensionImages.get(desc); |
| if (anImage != null) { |
| return anImage; |
| } |
| anImage = desc.getImageDescriptor(); |
| extensionImages.put(desc, anImage); |
| return anImage; |
| } |
| } |
| // Lookup in the cache first... |
| String key = mappingKeyFor(filename); |
| ImageDescriptor anImage = extensionImages.get(key); |
| if (anImage != null) { |
| return anImage; |
| } |
| |
| // See if we have a mapping for the filename or extension |
| FileEditorMapping[] mapping = getMappingForFilename(filename); |
| for (int i = 0; i < 2; i++) { |
| if (mapping[i] != null) { |
| // Lookup in the cache first... |
| String mappingKey = mappingKeyFor(mapping[i]); |
| ImageDescriptor mappingImage = extensionImages.get(key); |
| if (mappingImage != null) { |
| return mappingImage; |
| } |
| // Create it and cache it |
| IEditorDescriptor editor = mapping[i].getDefaultEditor(); |
| if (editor != null) { |
| mappingImage = editor.getImageDescriptor(); |
| extensionImages.put(mappingKey, mappingImage); |
| return mappingImage; |
| } |
| } |
| } |
| |
| // Nothing - time to look externally for the icon |
| anImage = getSystemExternalEditorImageDescriptor(filename); |
| if (anImage == null) { |
| anImage = getDefaultImage(); |
| } |
| // for dynamic UI - comment out the next line |
| // extensionImages.put(key, anImage); |
| return anImage; |
| |
| } |
| |
| /** |
| * Find objects related to the content type. |
| * |
| * This method is temporary and exists only to back us off of the |
| * soon-to-be-removed IContentTypeManager.IRelatedRegistry API. |
| * |
| * @param type |
| * @param fileName |
| * @param registry |
| * @return the related objects |
| */ |
| private IEditorDescriptor[] findRelatedObjects(IContentType type, String fileName, RelatedRegistry registry) { |
| List<IEditorDescriptor> allRelated = new ArrayList<>(); |
| List<IEditorDescriptor> nonDefaultFileEditors = new ArrayList<>(); |
| |
| if (fileName != null) { |
| FileEditorMapping mapping = getMappingFor(fileName); |
| if (mapping != null) { |
| // backwards compatibility - add editors flagged as "default" |
| IEditorDescriptor[] related = mapping.getDeclaredDefaultEditors(); |
| for (IEditorDescriptor editor : related) { |
| // we don't want to return duplicates |
| if (editor != null && !allRelated.contains(editor)) { |
| // if it's not filtered, add it to the list |
| if (!WorkbenchActivityHelper.filterItem(editor)) { |
| allRelated.add(editor); |
| } |
| } |
| } |
| |
| // add all filename editors to the nonDefaultList |
| // we'll later try to add them all after content types are resolved |
| // duplicates (ie: default editors) will be ignored |
| nonDefaultFileEditors.addAll(Arrays.asList(mapping.getEditors())); |
| } |
| |
| int index = fileName.lastIndexOf('.'); |
| if (index > -1) { |
| String extension = "*" + fileName.substring(index); //$NON-NLS-1$ |
| mapping = getMappingFor(extension); |
| if (mapping != null) { |
| IEditorDescriptor[] related = mapping.getDeclaredDefaultEditors(); |
| for (IEditorDescriptor editor : related) { |
| // we don't want to return duplicates |
| if (editor != null && !allRelated.contains(editor)) { |
| // if it's not filtered, add it to the list |
| if (!WorkbenchActivityHelper.filterItem(editor)) { |
| allRelated.add(editor); |
| } |
| } |
| } |
| nonDefaultFileEditors.addAll(Arrays.asList(mapping.getEditors())); |
| } |
| } |
| } |
| |
| if (type != null) { |
| // now add any objects directly related to the content type |
| IEditorDescriptor[] related = registry.getRelatedObjects(type); |
| for (int i = 0; i < related.length; i++) { |
| // we don't want to return duplicates |
| if (!allRelated.contains(related[i])) { |
| // if it's not filtered, add it to the list |
| if (!WorkbenchActivityHelper.filterItem(related[i])) { |
| allRelated.add(related[i]); |
| } |
| } |
| } |
| |
| } |
| |
| if (type != null) { |
| // now add any indirectly related objects, walking up the content type hierarchy |
| while ((type = type.getBaseType()) != null) { |
| IEditorDescriptor[] related = registry.getRelatedObjects(type); |
| for (int i = 0; i < related.length; i++) { |
| // we don't want to return duplicates |
| if (!allRelated.contains(related[i])) { |
| // if it's not filtered, add it to the list |
| if (!WorkbenchActivityHelper.filterItem(related[i])) { |
| allRelated.add(related[i]); |
| } |
| } |
| } |
| } |
| } |
| |
| // add all non-default editors to the list |
| for (IEditorDescriptor editor : nonDefaultFileEditors) { |
| if (editor != null && !allRelated.contains(editor) && !WorkbenchActivityHelper.filterItem(editor)) { |
| allRelated.add(editor); |
| } |
| } |
| |
| return allRelated.toArray(new IEditorDescriptor[allRelated.size()]); |
| } |
| |
| /** |
| * Return the editors bound to this content type, either directly or indirectly. |
| * |
| * @param type the content type to check |
| * @return the editors |
| * @since 3.1 |
| * |
| * TODO: this should be rolled in with the above findRelatedObjects code |
| */ |
| public IEditorDescriptor[] getEditorsForContentType(IContentType type) { |
| List<IEditorDescriptor> allRelated = new ArrayList<>(); |
| if (type == null) { |
| return new IEditorDescriptor[0]; |
| } |
| |
| IEditorDescriptor[] related = relatedRegistry.getRelatedObjects(type); |
| for (int i = 0; i < related.length; i++) { |
| // we don't want to return duplicates |
| if (!allRelated.contains(related[i])) { |
| // if it's not filtered, add it to the list |
| if (!WorkbenchActivityHelper.filterItem(related[i])) { |
| allRelated.add(related[i]); |
| } |
| |
| } |
| } |
| |
| // now add any indirectly related objects, walking up the content type hierarchy |
| while ((type = type.getBaseType()) != null) { |
| related = relatedRegistry.getRelatedObjects(type); |
| for (int i = 0; i < related.length; i++) { |
| // we don't want to return duplicates |
| if (!allRelated.contains(related[i])) { |
| // if it's not filtered, add it to the list |
| if (!WorkbenchActivityHelper.filterItem(related[i])) { |
| allRelated.add(related[i]); |
| } |
| } |
| } |
| } |
| |
| return allRelated.toArray(new IEditorDescriptor[allRelated.size()]); |
| } |
| |
| /** |
| * Get file mappings for all defined file types, including those defined by |
| * content type. |
| * |
| * @return the file types |
| * @since 3.1 |
| */ |
| public IFileEditorMapping[] getUnifiedMappings() { |
| IFileEditorMapping[] standardMappings = PlatformUI.getWorkbench().getEditorRegistry().getFileEditorMappings(); |
| |
| List<IFileEditorMapping> allMappings = new ArrayList<>(Arrays.asList(standardMappings)); |
| // mock-up content type extensions into IFileEditorMappings |
| for (IContentType type : contentTypeManager.getAllContentTypes()) { |
| for (String extension : type.getFileSpecs(IContentType.FILE_EXTENSION_SPEC)) { |
| boolean found = false; |
| for (IFileEditorMapping mapping : allMappings) { |
| if ("*".equals(mapping.getName()) && extension.equals(mapping.getExtension())) { //$NON-NLS-1$ |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| MockMapping mockMapping = new MockMapping(type, "*", extension); //$NON-NLS-1$ |
| allMappings.add(mockMapping); |
| } |
| } |
| |
| for (String wholename : type.getFileSpecs(IContentType.FILE_NAME_SPEC)) { |
| int idx = wholename.indexOf('.'); |
| String name = idx == -1 ? wholename : wholename.substring(0, idx); |
| String extension = idx == -1 ? "" : wholename.substring(idx + 1); //$NON-NLS-1$ |
| |
| boolean found = false; |
| for (IFileEditorMapping mapping : allMappings) { |
| if (name.equals(mapping.getName()) && extension.equals(mapping.getExtension())) { |
| found = true; |
| break; |
| } |
| } |
| if (!found) { |
| MockMapping mockMapping = new MockMapping(type, name, extension); |
| allMappings.add(mockMapping); |
| } |
| } |
| } |
| |
| return allMappings.toArray(new IFileEditorMapping[allMappings.size()]); |
| } |
| |
| /** |
| * @param contentType |
| * @param editor |
| * @return whether the association between content-type and editor was defined |
| * in user space |
| */ |
| public boolean isUserAssociation(IContentType contentType, IEditorDescriptor editor) { |
| return this.contentTypeToEditorMappingsFromUser.containsKey(contentType) |
| && this.contentTypeToEditorMappingsFromUser.get(contentType).contains(editor); |
| } |
| |
| /** |
| * @param contentType |
| * @param editor |
| */ |
| public void removeUserAssociation(IContentType contentType, IEditorDescriptor editor) { |
| if (this.contentTypeToEditorMappingsFromUser.containsKey(contentType)) { |
| this.contentTypeToEditorMappingsFromUser.get(contentType).remove(editor); |
| } |
| saveAssociations(); |
| } |
| |
| /** |
| * @param contentType |
| * @param selectedEditor |
| */ |
| public void addUserAssociation(IContentType contentType, IEditorDescriptor selectedEditor) { |
| if (!this.contentTypeToEditorMappingsFromUser.containsKey(contentType)) { |
| this.contentTypeToEditorMappingsFromUser.put(contentType, new LinkedHashSet<>()); |
| } |
| if (!mapIDtoEditor.containsKey(selectedEditor.getId())) { |
| mapIDtoEditor.put(selectedEditor.getId(), selectedEditor); |
| } |
| this.contentTypeToEditorMappingsFromUser.get(contentType).add(selectedEditor); |
| saveAssociations(); |
| } |
| |
| } |
| |
| class MockMapping implements IFileEditorMapping { |
| |
| private IContentType contentType; |
| private String extension; |
| private String filename; |
| |
| MockMapping(IContentType type, String name, String ext) { |
| this.contentType = type; |
| this.filename = name; |
| this.extension = ext; |
| } |
| |
| @Override |
| public IEditorDescriptor getDefaultEditor() { |
| IEditorDescriptor[] candidates = ((EditorRegistry) PlatformUI.getWorkbench().getEditorRegistry()) |
| .getEditorsForContentType(contentType); |
| if (candidates.length == 0 || WorkbenchActivityHelper.restrictUseOf(candidates[0])) { |
| return null; |
| } |
| return candidates[0]; |
| } |
| |
| @Override |
| public IEditorDescriptor[] getEditors() { |
| IEditorDescriptor[] editorsForContentType = ((EditorRegistry) PlatformUI.getWorkbench().getEditorRegistry()) |
| .getEditorsForContentType(contentType); |
| return (IEditorDescriptor[]) WorkbenchActivityHelper.restrictArray(editorsForContentType); |
| } |
| |
| @Override |
| public IEditorDescriptor[] getDeletedEditors() { |
| return new IEditorDescriptor[0]; |
| } |
| |
| @Override |
| public String getExtension() { |
| return extension; |
| } |
| |
| @Override |
| public ImageDescriptor getImageDescriptor() { |
| IEditorDescriptor editor = getDefaultEditor(); |
| if (editor == null) { |
| return WorkbenchImages.getImageDescriptor(ISharedImages.IMG_OBJ_FILE); |
| } |
| |
| return editor.getImageDescriptor(); |
| } |
| |
| @Override |
| public String getLabel() { |
| return filename + '.' + extension; |
| } |
| |
| @Override |
| public String getName() { |
| return filename; |
| } |
| |
| @Override |
| public boolean equals(Object obj) { |
| if (this == obj) { |
| return true; |
| } |
| |
| if (!(obj instanceof MockMapping)) { |
| return false; |
| } |
| |
| MockMapping mapping = (MockMapping) obj; |
| return this.filename.equals(mapping.filename) && this.extension.equals(mapping.extension) |
| && Arrays.equals(this.getEditors(), mapping.getEditors()) |
| && Arrays.equals(this.getDeletedEditors(), mapping.getDeletedEditors()); |
| } |
| } |