No Business Naming Things

Where sassy women wear jaunty hats.

No Business Naming Things header image 2

Link To Editor

October 27th, 2006 · 4 Comments

Another question from the inbox (paraphrased just a little):

Yo, Kim. What’s the deal with Link To Editor functionality? How do I make my view exhibit this behavior? PS: I love your hat.

In case you aren’t familiar with “Link To Editor”, it is the notion that certain views can repopulate/select content within themselves based on the active editor. You see it often in views such as the Package Explorer and the History View. When an editor is activate the file is selected and scrolled to in the Package Explorer and the History View refreshes and shows new history.

There isn’t really any “deal” with this functionality. Despite its pervasiveness there isn’t any official API support for Link To Editor behavior. In all cases the support is home rolled. Thankfully, it’s pretty easy to accomplish.

Step one, define a “Link To Editor” action in your view. This often appears on the view Toolbar as a toggle action can be added in your views

createPartControl

method as follows:

public void createPartControl(Composite parent) {
// add interesting controls
getViewSite().getActionBars().getToolBarManager().add(
new Action(”Link With Editor”, IAction.AS_CHECK_BOX) {
public void run() {
toggleLinking(isChecked());
}
});
}

The toggleLinking method will look something like this:

protected void toggleLinking(boolean checked) {
this.linking = checked;
if (checked)
editorActivated(getSite().getPage().getActiveEditor());
}

This method is pretty straight forward. It preserves the linking state and then if we’re now linking calls

editorActivated

with the currently active editor (if any). This method does the bulk of our work and is called here to refresh the state of our view based on the active editor. It looks like this:

private void editorActivated(IEditorPart editor) {
if (!linking || !getViewSite().getPage().isPartVisible(this))
return;

IEditorInput input = editor.getEditorInput();

IFile file = ResourceUtil.getFile(input);
if (file != null) {
// do something interesting
}
}

The above implementation of

editorActivated

assumes that our view is a resource view of some kind but that doesn’t have to be the case. You can implement linked views based on any editor input you want. For example, where “

// do something interesting

” is above you could create a

org.eclipse.jface.viewers.StructuredSelection

and set your views TreeViewer selection to that object. What the linking itself means is up to your view but the above pattern will establish the behaviour. Also note the eager return at the beginning of the method. If we aren’t currently in linking mode or we’re not visible then we shouldn’t do the linking work. Good citizens of the Eclipse ecosystem are coded to only do work when necessary. If we’re not visible then there’s no need for us to do the heavy work of linking editor content to our own.

We now have a link that is refreshed whenever the action is toggled. To make it useful we’ll need to hook the

editorActivated

method up to editor activation. First, add the following field to your view class:

private IPartListener2 partListener2 = new IPartListener2() {
public void partActivated(IWorkbenchPartReference ref) {
if (ref.getPart(true) instanceof IEditorPart)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partBroughtToTop(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partClosed(IWorkbenchPartReference ref) {}

public void partDeactivated(IWorkbenchPartReference ref) {}

public void partOpened(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partHidden(IWorkbenchPartReference ref) {}

public void partVisible(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this) editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partInputChanged(IWorkbenchPartReference ref) {}
};

In the

createPartControl

method add the following code sometime after the addition of the toggle action:

getSite().getPage().addPartListener(partListener2);

Finally, in the views dispose method add the following:

getSite().getPage().removePartListener(partListener2);

The listener above perform the following actions. When an editor is opened, the

editorActivated

method is called and our view may be updated (if linking is enabled and it’s visible). Also, if a part is opened and it’s our view then the

editorActivated

method is called. This refreshes the linked state on startup and delayed opening of our view. Finally, if our part is made visible the linked state is refreshed. This can happen in certain circumstances when plug-in code calls the

org.eclipse.ui.IWorkbenchPage.showView

method with the

IWorkbenchPage.VIEW_VISIBLE

flag as an argument. We now have a view that implements remedial Link To Editor behavior. The one thing that this example is missing is the persistence of the link state between restarts. This could be done via preferences or via the

org.eclipse.ui.IMemento

object passed to the

init

and

saveState

methods.

Something I want to point out (that isn’t specific to this example) is the use of abstract inner class listeners on global collections (such as workbench page). In the above example you could easily make the mistake of creating the

IPartListener2

implementations in the

createPartControl

method and neglect to dispose of them. This is a VERY BAD THING. Without the explicit disposal your view will never go away. Even if the user closes the view it will still reside in memory, forever reacting to part events that are no longer relevant to it. Abstract inner classes retain a reference to their host class and without proper disposal the leak of an object of an inner class also leaks the host object. Always take out your trash!

Here is view code in its entirety that simply spits out the name of the current file if Link To Editor is enabled:

import org.eclipse.core.resources.IFile;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IAction;
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Text;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IPartListener2;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.ui.IWorkbenchPartReference;
import org.eclipse.ui.ide.ResourceUtil;
import org.eclipse.ui.part.ViewPart;

public class LinkedWithEditorView extends ViewPart {

private Text text;

private boolean linking;

private IPartListener2 partListener2 = new IPartListener2() {
public void partActivated(IWorkbenchPartReference ref) {
if (ref.getPart(true) instanceof IEditorPart) {
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partBroughtToTop(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partClosed(IWorkbenchPartReference ref) {}

public void partDeactivated(IWorkbenchPartReference ref) {}

public void partOpened(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partHidden(IWorkbenchPartReference ref) {}

public void partVisible(IWorkbenchPartReference ref) {
if (ref.getPart(true) == LinkedWithEditorView.this)
editorActivated(getViewSite().getPage().getActiveEditor());
}

public void partInputChanged(IWorkbenchPartReference ref) {}
};

public LinkedWithEditorView() { }

public void createPartControl(Composite parent) {
text = new Text(parent, SWT.READ_ONLY);
getViewSite().getActionBars().getToolBarManager().add(
new Action(”Link With Editor”, IAction.AS_CHECK_BOX) {

public void run() {
toggleLinking(isChecked());
}
});

getSite().getPage().addPartListener(partListener2);
}

public void dispose() {
getSite().getPage().removePartListener(partListener2);
}

protected void toggleLinking(boolean checked) {
this.linking = checked;

if (checked)
editorActivated(getSite().getPage().getActiveEditor());

}

private void editorActivated(IEditorPart editor) {
if (!linking || !getViewSite().getPage().isPartVisible(this))
return;

IEditorInput input = editor.getEditorInput();

IFile file = ResourceUtil.getFile(input);
if (file != null)
text.setText(file.getLocation().toOSString());

}

public void setFocus() {
text.setFocus();
}
}

Edit: based on feedback I’ve changed the solution to only use one listener instead of two.

Tags: Eclipse

4 responses so far ↓

  • 1 Mark Drew // Jan 11, 2007 at 10:16 am

    Thanks for that code Kim, definately useful for something I was trying to do. Although, how would you listen to changes in the linked document (such as when its saved or modified)?

  • 2 pookzilla // Jan 11, 2007 at 10:26 am

    Mark:
    You could easily add an IResourceChangeListener into the mix that would react to changes to the resource.

  • 3 Anonymous // Feb 8, 2007 at 7:11 am

    Cool,it possible to back port it to 3.2.1. Will be greatfull if u got any info to that.

  • 4 Prajwalan Karanjit // Jul 30, 2008 at 8:43 am

    Really a cool artice. Great work Kim.
    And thank you very much.

Leave a Comment