Eclipse Plugin to handle same file extension with different editors
I am writing a plugin for Eclipse which provides syntax highlighting for a specific preprocessor directive that is used in our software stack. Before compiling the actual code, these get replaced开发者_如何学Go by an external program (imagine a tag like <% FOO %>
which gets replaced with something else, say a version string).
So the plugin provides annotations for each tag to quickly identify these. Instead of implementing an own editor, the annotations are provided for already existing editors, like the JavaEditor or PHP-Editor.
Now the problem is that the files which get processed by the external program all have the same file extension, say .inc
. They can contain Java code or PHP code (other languages also possible in the future).
I have successfully added a content type to my plugin and I can distinguish the different files based on some criteria. So, when I have a java .inc
file and assign the content type inc file (java)
.
However, the user should be able to overwrite this auto-detection (also, sometimes the auto-detection might fail). So, I want to be able to open one file (foo.inc
) with different editors (Java Editor, PHP Editor, …) and be able to save this association.
The approaches I am currently thinking of:
- Overwriting the file open action to check a setting in my project and open the appropriate editor. I did not find a solution that covers how to overwrite all file open actions (File → Open File in the main menu, Open in the Project Navigator, …)
- Implementing an own editor which then opens the appropriate editor. This seems to be a hackish approach which would also cause some delay.
- In the "open with" context menu on a file, the user can change the editor. Changing it programmatically would be OK, but I cannot find an API or the file in which this choice is stored.
- Implementing different Natures for a project, then associate the file types differently in the context of each nature. Would only provide a project-specific association, not a per-file one.
Are there better solutions? Do you know more about any of the approaches I have listed?
I found out how to overwrite the file open action: by registering an actionProvider
which overrides org.eclipse.ui.navigator.resources.OpenActions
. I provide all the code because it is relatively hard to put all these different things together and have it working.
Let’s start with the plugin.xml entry:
<!-- overwrite OpenActions -->
<extension
point="org.eclipse.ui.navigator.navigatorContent">
<actionProvider
class="myplugin.navigator.OpenActionProvider"
id="myplugin.navigator.actions.open"
overrides="org.eclipse.ui.navigator.resources.OpenActions"
priority="highest">
<enablement>
<and>
<instanceof
value="org.eclipse.core.resources.IFile">
</instanceof>
<test
property="org.eclipse.core.resources.extension"
value="frm">
</test>
</and>
</enablement>
</actionProvider>
</extension>
<extension
point="org.eclipse.ui.navigator.viewer">
<viewerActionBinding
viewerId="org.eclipse.ui.navigator.ProjectExplorer">
<includes>
<actionExtension
pattern="myplugin.navigator.actions.open">
</actionExtension>
</includes>
</viewerActionBinding>
</extension>
The OpenActionProvider looks like this:
package myplugin.navigator;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.actions.ActionContext;
import org.eclipse.ui.navigator.CommonActionProvider;
import org.eclipse.ui.navigator.ICommonActionConstants;
import org.eclipse.ui.navigator.ICommonActionExtensionSite;
import org.eclipse.ui.navigator.ICommonViewerWorkbenchSite;
public class OpenActionProvider extends CommonActionProvider {
private OpenEditorActionGroup fOpenGroup;
@Override
public void init(ICommonActionExtensionSite site) {
ICommonViewerWorkbenchSite workbenchSite = null;
if (site.getViewSite() instanceof ICommonViewerWorkbenchSite) {
workbenchSite = (ICommonViewerWorkbenchSite) site.getViewSite();
}
if (workbenchSite != null) {
if (workbenchSite.getPart() != null && workbenchSite.getPart() instanceof IViewPart) {
IViewPart viewPart = (IViewPart) workbenchSite.getPart();
fOpenGroup = new OpenEditorActionGroup(viewPart);
}
}
}
@Override
public void dispose() {
if (fOpenGroup != null) {
fOpenGroup.dispose();
fOpenGroup = null;
}
super.dispose();
}
@Override
public void fillActionBars(IActionBars actionBars) {
if (fOpenGroup == null)
return;
fOpenGroup.updateActionBars();
actionBars.setGlobalActionHandler(ICommonActionConstants.OPEN, fOpenGroup.getOpenAction());
}
@Override
public void fillContextMenu(IMenuManager menu) {
if (fOpenGroup == null)
return;
fOpenGroup.fillContextMenu(menu);
}
@Override
public void setContext(ActionContext context) {
super.setContext(context);
if (fOpenGroup == null)
return;
fOpenGroup.setContext(context);
}
}
The OpenEditorActionGroup looks like this:
package myplugin.navigator;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.jface.action.IAction;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.action.MenuManager;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.viewers.StructuredSelection;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.actions.OpenFileAction;
import org.eclipse.ui.actions.OpenWithMenu;
import org.eclipse.ui.navigator.ICommonMenuConstants;
public class OpenEditorActionGroup extends AbstractActionGroup {
private OpenFileAction fOpenFileAction;
public OpenEditorActionGroup(IViewPart viewPart) {
super(viewPart);
}
@Override
protected void makeActions() {
fOpenFileAction= new OpenGenElementAction(getViewPart().getSite().getPage());
}
public static IStructuredSelection convertSelectionToResources(ISelection s) {
List<Object> converted = new ArrayList<Object>();
if (s instanceof StructuredSelection) {
Object[] elements = ((StructuredSelection) s).toArray();
for (int i = 0; i < elements.length; i++) {
Object e = elements[i];
if (e instanceof IResource) {
converted.add(e);
} else if (e instanceof IAdaptable) {
IResource r = (IResource) ((IAdaptable) e).getAdapter(IResource.class);
if (r != null) {
converted.add(r);
}
}
}
}
return new StructuredSelection(converted.toArray());
}
@Override
public void fillContextMenu(IMenuManager menu) {
System.out.println("fillcontextmenu");
if (getContext() == null)
return;
IStructuredSelection celements = (IStructuredSelection)getContext().getSelection();
IStructuredSelection selection = convertSelectionToResources(celements);
fOpenFileAction.selectionChanged(celements);
if (!fOpenFileAction.isEnabled())
return;
menu.appendToGroup(ICommonMenuConstants.GROUP_OPEN, fOpenFileAction);
fillOpenWithMenu(menu, selection);
}
The AbstractActionGroup is just a wrapper, should you want to implement more of these:
package myplugin.navigator;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.IActionBars;
import org.eclipse.ui.IViewPart;
import org.eclipse.ui.actions.ActionGroup;
public abstract class AbstractActionGroup extends ActionGroup {
private final IViewPart fViewPart;
public AbstractActionGroup(IViewPart viewPart) {
Assert.isNotNull(viewPart);
fViewPart = viewPart;
makeActions();
}
protected IViewPart getViewPart() {
return fViewPart;
}
protected ImageDescriptor getImageDescriptor(String relativePath) {
return ImageDescriptor.createFromURL(null);
}
protected abstract void makeActions();
@Override
public abstract void fillContextMenu(IMenuManager menu);
@Override
public abstract void fillActionBars(IActionBars actionBars);
@Override
public abstract void updateActionBars();
}
And finally the OpenGenElementAction itself:
package myplugin.navigator;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IEditorDescriptor;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.ui.PartInitException;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.OpenFileAction;
import org.eclipse.ui.editors.text.EditorsUI;
import org.eclipse.ui.part.FileEditorInput;
public class OpenGenElementAction extends OpenFileAction {
private IFile selectedFile = null;
private final IWorkbenchPage workbenchPage;
public OpenGenElementAction(IWorkbenchPage page) {
super(page);
workbenchPage = page;
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public void run() {
System.out.println("RUN");
}
@Override
protected boolean updateSelection(IStructuredSelection selection) {
if (selection.size() != 1)
return super.updateSelection(selection);
Object element = selection.getFirstElement();
if (element instanceof IFile) {
selectedFile = (IFile)element;
}
return selectedFile != null || super.updateSelection(selection);
}
}
You could attack directly the IEditorRegistry programmatically, by registering specific file names rather than just extensions.
Look at : "Help on IEditorRegistry"
http://help.eclipse.org/luna/index.jsp?topic=%2Forg.eclipse.platform.doc.isv%2Freference%2Fapi%2Forg%2Feclipse%2Fui%2FIEditorRegistry.html
The method :
void setDefaultEditor(String fileNameOrExtension, String editorId)
Sets the default editor id for the files that match
that specified file name or extension.
accepts full names and/or wildcards. Most of the openEditor calls (menus, toolbars etc...) end up in calls to this registry to get the appropriate editor. Set up a hook when opening your editor that registers this file name specifically.
It's not foolproof if you have two files with same name and different language but it's fast and easy to implement compared to your approaches.
精彩评论