Thursday, November 1, 2007

AbstractRulerColumn text inset

Recently, I've been trying to learn about Eclipse's text processing and display APIs (org.eclipse.text and org.eclipse.jface.text). I wanted to determine how eclipse dynamically placed its line numbers beside its text editors. Then, I wanted to determine how I could customize the text in that area.

For non-graphical text processing, Eclipse provides the IDocument interface. I've yet to do much exploration into its capabilities, but for now it gives me the ability to determine line counts.

For text display, Eclipse provides the TextViewer and the SourceViewer. The TextViewer is to a StyledText what a TableViewer is to a Table. The SourceViewer extends the TextViewer and adds an IVerticalRuler and IOverviewRuler. The IVerticalRuler allows you to draw arbitrary content on the left of an ISourceViewer; the IOverviewRuler does the same on the right. In terms of the JDT, it looks like the line numbers are an IVerticalRuler and the source annotations (the red/yellow bars on the right for problems/warnings) are an IOverviewRuler.

Experienced JDT users will remember that the JDT has more than one column to the left of its source code editors. Eclipse provides reusable code for a multi-column IVerticalRuler: the CompositeRuler. It allows clients to compose a single IVerticalRuler from many IVerticalRulerColumns, and the JDT uses it to hold its line number column and its code folding column.

Eclipse provides the AbstractRulerColumn for simple columns like mine. I just want to replicate eclipse's LineNumberRulerColumn. (The LineNumberRulerColumn has fancy features like right alignment, double-buffering, and performance tweaks that I don't need; I'm just learning here.) All I need to do is extend AbstractRulerColumn#computeText(int) to create text for a given line of my line number column. Additionally, I must use AbstractRulerColumn#setWidth(int) to update the width requirements when my lines grow by a power of 10. Finally, I must ensure to set my AbstractRulerColumn's textInset to zero. It took me 2 hours to track down why my line numbers were getting chopped off.

Conveniently, the AbstractRulerColumn is a PaintListener to its underlying Canvas, and will query its subclass for text on every repaint. However, just because my line numbers changed doesn't mean my AbstractRulerColumn's Canvas will be asked to paint itself, so I need to listen for text changes. Because SourceViewer subclasses TextViewer, I can listen for its TextEvents with an ITextListener. Then, when the SourceViewer's text changes, I can ask my AbstractRulerColumn to update itself with a call to AbstractRulerColumn#redraw().