Eclipse: Rich Hovers Redux

In Using the new Rich Text Hovers in Eclipse 3.4, I, uh, suggested that perhaps, um, maybe, an implementation of ITextHoverExtension2#getHoverInfo2 (from the package org.eclipse.jface.text) could return either a String or “An implementation of IInformationControlExtension2“.

That was a m- mi- mis- mistake. What I meant to say was:

[It] is the responsibility of the implementer of IInformationControl and IInformationControlExtension2 to specify the concrete nature of the information control’s input…

In plain(er) English: If you write the hover control, you get to specify the hover information. The implementation of getHoverInfo2 must be aware of the hover control’s implementation, and must conform to its specification.

So as a plug-in author, how can you create hovers with rich display formats? I’ve found two options. The first of these doesn’t even require any of the extensions that I’ve already touched upon.

Using the DefaultHoverControl

Straight out of the box (as it were), the JavaEditor example plug-in is configured to use the default version of the org.eclipse.jface.text.DefaultHoverControl class. In this configuration, the DefaultHoverControl treats its text input as raw text (without markup), as shown in the screen shot below.

(Note: The example Java editor plug-in simply copies the current text selection into the hover. In these screen shots, the selection is a Java comment that contains HTML markup.)

Screen shot of Java editor plug-in example, showing HTML markup passed through as text.

Some of the constructors for DefaultHoverControl enable the control to process a subset of HTML. To use one of these, we need to add the following method to JavaSourceViewerConfiguration in the sample editor; this overrides the base class implementation in org.eclipse.jface.text.source.SourceViewerConfiguration :

/**
 * Returns the information control creator. The creator is a 
 * factory creating information controls for the given source
 * viewer. This implementation always returns a creator for
 * <code>JavaInformationControl</code> instances.
 *
 * @param sourceViewer the source viewer to be configured
 *        by this configuration
 * @return the information control creator, or <code>null</code> 
 *         if no information support should be installed
 * @since 2.0
 */
public IInformationControlCreator 
getInformationControlCreator(ISourceViewer sourceViewer) {
   return new IInformationControlCreator() {
      public IInformationControl createInformationControl(Shell parent) {
          return new JavaHoverInformationControl(parent);
      }
   };
}

Unlike the base class implementation, this version calls the two-argument constructor DefaultInformationControl(parent, false). Internally, this constructor will create an HTMLTextPresenter object, which supports some simple HTML markup.

The emphasis here is on simple HTML. Unfortunately, while HTMLTextPresenter happily reads the HTML markup, it doesn’t implement very much of it. As this screenshot illustrates, both <code> and <em> markup are ignored. (There’s no example hyperlink here, but I can confirm that they’re ignored as well.)

Another Java editor plug-in example, showing partially supported HTML markup.

Creating a new hover control

HoveringIf you need to support richer content than DefaultInformationControl can handle, then it’s likely that you’ll need to create your own hover control. This may require some experimentation, as the definitive guide to writing hover controls hasn’t been written yet (or if it has been, its visibility flag is set to false.)

I’ve put together a demo of a hover control that uses the SWT Browser widget. The JavaHoverInformationControl class implementation requires only about 150 lines of code, plus a few changes in two other files in the JavaEditor demo. It’s still rough around the edges; you may not be replacing your desktop browser with it anytime soon, but it should help to illustrate how a plug-in author can take control over hover controls.

In this screenshot, you can see that the example comment is now displayed in all of its HTML glory in the hover:

The JavaHoverInformationControl, showing HTML displayed in a small Browser widget.

For additional demo value—and to show how this all ties in with ITextHoverExtension2#getHoverInfo2—the JavaHoverInformationControl also displays something useful when no text is currently selected:

It isn't all that useful, really.
(Well, it’s useful to me, anyway!)

A few aspects of the demo are worth describing here. The tie-in to ITextHoverExtension2#getHoverInfo2 is through an interface nested within the new JavaHoverInformationControl class:

public interface IHTMLHoverInfo {
    /**
     * @return true if the String returned by getHTMLString()
     *  represents a URL;  false if the String contains marked-up text.
     */
    public boolean isURL();

    /**
     * @return The input string to be displayed in the Browser widget
     *  (either as marked-up text, or as a URL.)
     */
    public String getHTMLString();
}

This is reflected in the JavaTextHover class, which I’ve modified to implementITextHoverExtension2#getHoverInfo2. This implementation returns an Object that conforms to the IHTMLHoverInfo interface:

public class JavaTextHover 
                implements ITextHover, ITextHoverExtension2 {
//... other methods are not shown here...
public Object getHoverInfo2(ITextViewer textViewer, 
                                      IRegion hoverRegion) {

    // Start with the string returned by the older getHoverInfo()
    final String selection = getHoverInfo(textViewer, hoverRegion);

   // If text is selected in the editor window, it's returned as the
   // hover string. If no text is selected, then the returned hover is
   // a URL pointing to www.outofwhatbox.com/blog.
    return new JavaHoverInformationControl.IHTMLHoverInfo() {
        public boolean isURL() {return selection.length() == 0;}
        public String getHTMLString() {
            if (isURL()){
                return "http://www.outofwhatbox.com/blog";
            }
            return selection;
        }
    };
}

Similarly, the JavaHoverInformationControl#setObject method will invoke this object’s isURL method, indicating whether to handle the input as marked-up text or as a URL:

    public void setInput(Object input) {
        // Assume that the input is marked-up text, not a URL
        fIsURL = false;
        final String inputString;

        if (input instanceof IHTMLHoverInfo) {
            // Get the input string, then see whether it's a URL
            IHTMLHoverInfo inputInfo = (IHTMLHoverInfo) input;
            inputString = inputInfo.getHTMLString();
            fIsURL= inputInfo.isURL();

//... rest of the code not shown here...

I’ve packaged the JavaHoverInformationControl implementation with the modified source from the sample org.eclipse.ui.examples.javaeditor package. It’s available for download as a ZIP file file and as a gzip’d tar file.

Using the new Rich Text Hovers in Eclipse 3.4

I’m moving away from writing about Agile for a time, to look at some other programming topics. I know that with a new blog, it may be a mistake to switch subjects so abruptly. But then, I’m all about making mistakes.

I’m studying the sample Eclipse plugin projects that come bundled with Eclipse for RCP/Plug-in Developers. I had some adventures trying to track down the cause of a deprecation warning in the first sample, org.eclipse.ui.examples.javaeditor:

The method JavaTextHover.getHoverInfo(ITextViewer, IRegion) overrides a deprecated method from ITextHover

This method became deprecated when Eclipse 3.4 added new methods for implementing text hovers (the little popups that give you information about the text under the cursor, aka “infopops”.) The sample application still uses the older, deprecated form. But what should newer applications use? The documentation for the ITextHover interface, which declares the deprecated method, was moderately helpful at best:

In order to provide backward compatibility for clients of ITextHover, extension interfaces are used as a means of evolution. The following extension interfaces exist:

[…snip…]

ITextHoverExtension2 since version 3.4 allowing a text hover to return hover-specific information objects.

This looked promising. But what’s an “extension interface?” And what does it extend?

Aha! It’s a mixin1. To use it, you modify your existing hover class so that it implements ItextHoverExtension2 as well as the legacy ITextHover interface. In the sample project, I modified the JavaTextHover class, changing this:

public class JavaTextHover implements ITextHover {

to add the new ITextHoverExtension2 interface :

public class JavaTextHover implements 
    ITextHover, ITextHoverExtension2 {
        // ... [other methods aren't shown here]  ...
        public Object getHoverInfo2(ITextViewer textViewer,
                                              IRegion hoverRegion) {
                // For now, just return the same value as 
                // the legacy getHoverInfo()
                return getHoverInfo(textViewer, hoverRegion);
        }

To verify this, I used its own debugger to peek inside Eclipse, to see how it uses the ITextHover object2. Setting a breakpoint on the new getHoverInfo2 method, I saw that the class org.eclipse.jface.text.TextViewerHoverManager detects the hover object’s interface(s) via reflection, and makes the appropriate call to the hover object:

if (hover instanceof ItextHoverExtension2)
    information=
        ((ITextHoverExtension2)hover).getHoverInfo2(fTextViewer, region);
else
    information= hover.getHoverInfo(fTextViewer, region);
EDIT: What I’d written here previously about the return value from getHoverInfo2 was inaccurate. I’ve written a new post that, I hope, provides a clearer picture.

Note that although the method ITextHover.getHoverInfo is now deprecated, its interface ITextHover is not deprecated, even though ITextHover has no other methods. In fact, a number of non-deprecated classes, including org.eclipse.jface.text.source.SourceViewerConfiguration, still use ITextHover as either a parameter or a return type. I haven’t seen any way to route around these classes. If there’s no way around the use of ITextHover, then the editor application is forced to continue implementing the deprecated getHoverInfo method.

I’m well aware that “deprecated” classes and methods tend to hang around. Just the same, I’d rather not be forced into writing deprected methods.


[1] I’m using the term “mixin” in its loosest sense, denoting an optional interface that a class may choose to implement.

[2] One could say that Eclipse’s debugger reflects well upon itself. (Sorry. I’m a reflexive punner.)

Living Agile

My plan was to quickly write a follow-up to How To Make Mistakes, aiming it more directly towards software developers. I’d planned to pick up where I’d left off, drawing a connection between my earlier topic and Agile software development. But inevitably, just as I sat down to write, a new idea poked its head in. Looking at me with its big, wide eyes, it asked me ever so sweetly: “Can I come in?” “Sure”, I said.

That was my first mistake. Sure enough, hot on its heels came another one, wearing a full-body smile. I couldn’t resist. “OK”, I said, “You can come in too.”

Then a third idea showed up, this one a bit more obstreperous. “You let them in. I want to come in too!” “Uhh… I guess so.”

Some of those ideas were so charming that they were hard to resist. But before I knew it, there was a crowd of them, each struggling to be the first to slide into the keyboard. With all of their jostling, none of them could get through. If I couldn’t break that logjam, this post was never going to happen. “Stop! Time out! All of you, go to your rooms! Now!” I hated to see the looks on their faces, but I had no choice. I’m trying to write a blankety-blank blog, not The Great American Software Book.

That crowd of ideas came out of some discussions about Agile software development at last month’s No Fluff Just Stuff conference in Boston. It’s fitting that in dismissing them (at least for now), I was applying a lesson from Agile: Don’t try to do everything at once. Create and deliver in small increments instead. You get feedback faster this way, and your customers get the use of your work sooner.

This isn’t the first time I’ve taken a concept from Agile to use in domains that are removed from software development. How To Make Mistakes was essentially a distillation of Agile concepts. I’d even suggest that one of Agile’s greatest strengths is that it helps to create a process for making better mistakes. Conversely, one of its greatest weaknesses may be that few people recognize this.

How does Agile help create better mistakes? To illustrate this, I’ll list the guidelines that I set out in How To Make Mistakes with their foundations in Agile:

  • Understand the risks: The most popular flavors of Agile, such as Scrum and Extreme Programming, emphasize specific methods for identifying and mititgating risks.
  • Define your goals, Seek feedback early and often, Assess and Adjust: I lifted these fairly directly from the Agile Principles.
  • Work consciously: This mirrors another Agile Principle: At regular intervals, the team reflects on how to become more effective, then tunes and adjusts its behavior accordingly. Such reflection can and should be a part of each developer’s personal work methods too.
  • Make your own mistakes. The Agile Manifesto states a preference for “Individuals and interactions over processes and tools.” Similarly, the Agile Principles declare that “the best architectures, requirements, and designs emerge from self-organizing teams.” Put together, these remind us: No process, however intelligently it was designed, possesses its own intelligence.  Human intelligence must be allowed to trump process guidelines. In fact, any well-designed process must include guidelines for overriding and updating itself. Agile processes are living processes.

One of the risks to successful Agile adoption is having unrealistic expectations. Business stakeholders may expect that a “better” software process is one that results in fewer mistakes being made. A more pragmatic expectation might be that Agile can help a team make better mistakes, which eventually will lead to fewer being made. Better mistakes and fewer mistakes both lead to better results.

I may have some other ideas on this, but they’ll have to wait for another time.

“Wheee!” [*Splash*!]

Back to your room! I’m closing this post down now!

How To Make Mistakes

A noted pianist recounted an important lesson he’d received in his conservatory days: He and a professor had attended a concert by one of the great performers of the 20th century, a man then nearing the end of his career. Afterwards, the student commented on the number of mistakes the master had made. His professor rejoined: My boy, I’d rather listen to his mistakes than to your best playing.

The professor’s meaning came through loud and clear to the student: The mistakes of a master may have greater merit than the rest of us can achieve with our best work.

I had a glimpse of this truth early in my career—not from an older master, but from a younger one. I was two years out of college when Jim (not his real name), fresh out of MIT, started working with me on a project. At first I was put off by this youngster’s apparent arrogance, but gradually it dawned on me that the guy knew what he was talking about. I began to seek his feedback on my work, and a good friendship developed between us.

Some time after Jim left the company, I told him that I’d been making a bit of a career fixing his mistakes. He was distressed to hear it. No worries, I assured him: he’d made the right mistakes. His work conveyed a good understanding of the problems that he’d been addressing, and was clear enough to help me absorb some of that same understanding. The overall structure was sound; the mistakes were usually in the details.

There was one arguable exception, though: Jim had solved a particularly thorny problem using a very non-traditional approach, which was difficult to maintain after he left. He’d been aware that this unusual technique could become a problem, but he’d concluded that the alternatives would have been worse. Regardless of whether Jim had made the best possible decision at the time, he deserves credit on several grounds: He’d considered different ways to solve the problem; he’d brought the best tools to the job that he could find; and above all else, he’d solved the problem. If his choice was a mistake, it was a mistake well worth making.

To err is human, or so it’s said. It may be even more human to fear erring. While this fear is often well-founded—it can, after all, save lives—it can also be costly. An excess of caution causes inaction, which often is also a mistake. And as has often been said, every mistake represents a learning opportunity. Since we are all bound to err sooner or later, it follows that we’d do better if we stopped trying to avoid mistakes, and started aiming to make the right mistakes.

Making the right mistakes requires using the right process. In fact, making the right mistakes is all about using the right process; what mistake you make matters far less than how you make it. The better your process for making mistakes, the better your mistakes will be. Drawing upon my vast experience at crafting mistakes, I offer herein some tips that may help you improve the quality of your own.

  • Understand the risks. When lives or vast fortunes are at stake, it’s probably not a good time for taking risks. Yet even when the real risks are lower than that, we still carry a severe aversion to mistakes. This can do more harm than good. Many of your mistakes will be forgotten in time; for others, you’ll have chances to make amends. Move your work forward based on what you know, or can learn fairly quickly.
  • Define your goals. What are the essential criteria? You and your customers need to agree on these points before much serious work can begin. Beyond the essentials, spend less time analyzing and defining, and more time doing. When choosing between building something good now, or something better later, go for the good. Today’s omissions will be tomorrow’s opportunities, if they are missed at all. Focusing on the essentials will help you respond to requests faster, which will help facilitate the next item on the list:
  • Seek feedback early and often. The sooner you discover your mistakes, the easier they’ll be to correct.
  • Work consciously. This isn’t some New Age concept, but a very down-to-earth process. Think of it as your personal continuous improvement or Kaizen plan:
    • Observe yourself as you work.
    • Consider what you’re doing, how you’re doing it, and why you’re doing it that way.
    • Look for better approaches. (There’s always a better way. When mistakes are possible, so is improvement.)
  • Assess and adjust. Look for your mistakes, take responsibility for them, and learn from them. Don’t let problems fester. Get other people involved, when you need to, but without seeking to assign or dodge blame.
  • Make your own mistakes. Understand what’s required. Listen closely to the advice and wisdom that other people have to offer you. But if you think that your best path goes down a different road than the one you’ve been pointed down, then follow that thought. Your successes will be sweeter, and your lessons will be learned more clearly.

There’s a question that’s posed in various places around the Internet:

What would you do, if you knew you could not fail?

That’s too broad for me to answer concisely. (Fix the current economic crisis? Bring peace to the world? Travel in time? My taxes?) Instead I ask myself a slightly different question:

What would you do if you had no fear of making mistakes?

I’d take piano lessons, and I’d write a blog.

One down, and one to go. I think I’m off to a pretty good start.

Update 08 Sep 2011

This is too good not to include here:
making decisions is hard