Jump to: navigation, search

Difference between revisions of "Word Wrap for Text Viewer and Editor"

m (Storing wrapped and unwrapped line number information about the same document: cat)
 
Line 174: Line 174:
 
If ILineTracker (implemented by WrappingLineTracker) is used for wrapping model then performance risks can be avoided.
 
If ILineTracker (implemented by WrappingLineTracker) is used for wrapping model then performance risks can be avoided.
 
Line information in WrappingLineTracker is stored as a binary tree and adding additional nodes for each line (1..number of lines after wrapping this line) does not look like a big performance issue. But this can be sure after WrappingLineTracker is properly implemented.
 
Line information in WrappingLineTracker is stored as a binary tree and adding additional nodes for each line (1..number of lines after wrapping this line) does not look like a big performance issue. But this can be sure after WrappingLineTracker is properly implemented.
 +
 +
[[Category:SOC]]

Latest revision as of 09:54, 14 October 2007

Project is part of Google_Summer_of_Code_2006

Student: Ahti Kitsik (ahti.kitsik@gmail.com)

Blog: http://ahtik.com/blog/

Mentor: Philippe Ombredanne

First "micro/alpha"-release is available at http://ahtik.com/blog/2006/06/18/first-alpha-of-eclipse-word-wrap-released/

Implementation notes -- developer-only reference

Related interfaces and classes

org.eclipse.ui.texteditor.ITextEditor

Interface to a text editor. This interface defines functional extensions to IEditorPart as well as the configuration capabilities of a text editor.

Text editors are configured with an IDocumentProvider which delivers a textual presentation (IDocument) of the editor's input. The editor works on the document and forwards all input element related calls, such as save, to the document provider. The provider also delivers the input's annotation model which is used to control the editor's vertical ruler.

org.eclipse.jface.text.IDocument

Represents text providing support for

  • text manipulation,
  • positions,
  • partitions,
  • line information,
  • document change listeners,
  • document partition change listeners


org.eclipse.jface.text.IDocumentInformationMapping

A IDocumentInformationMapping represents a mapping between the coordinates of two IDocument objects: the original and the image. The document information mapping can translate document information such as line numbers or character ranges given for the original into the corresponding information of the image and vice versa.

org.eclipse.jface.text.ITextStore

Storing and managing text.

org.eclipse.jface.text.ILineTracker

Maps character positions to line numbers and vice versa.

org.eclipse.jface.text.IDocumentAdapter

Adapts an org.eclipse.jface.text.IDocument to the org.eclipse.swt.custom.StyledTextContent interface. The document adapter is used by org.eclipse.jface.text.TextViewer to translate document changes into styled text content changes and vice versa.


Previously there seemed to be two candidates for managing and providing wrapping services:

  • IDocumentAdapter and
  • IDocumentInformationMapping.


Best Alternative: extending ILineTracker (more specifically TreeLineTracker)

ILineTracker AbstractDocument.getTracker() is using TreeLineTracker as it's implementation.

TreeLineTracker is using binary tree structure to hold information about lines, their lengths, offsets and delimiters. To support word wrap WrappingLineTracker is created based on TreeLineTracker. WrappingLineTracker provides additional structure to store wrapping-related information.

Wrapped model is always in sync with physical line model of WrappingLineTracker by changing TreeLineTracker.Node.setLength() method. For testing purposes wrapped model information is always returned for ILineTracker interface. At later stage additional interface will be created to support the idea of two line models (interface to get wrapped document model in addition to existing line model by ILineTracker).

One problem area: Line delimiters. Current TreeLineTracker implementation expects "" delimiter to be only at the last line of the model. But wrapped lines cannot contain delimiter.length>0 because then the length of the document does not match with the line model and an exception is thrown by StyledText while it's trying to display the document.

Older (outdated) alternatives

Two possible locations for patching:

  • Patching StyledText - this would become too bloated for keeping wrapped document model. In addition, as some visuals need wrapped and some need unwrapped document model then this would separate StyledText wrapped text from IDocument/TextViewer too much. Currently StyledText has WRAP support but it's not complete!
  • Patching TextViewer - this means patching one or several of it's accessories like IDocumentAdapter, IDocumentInformationMapping. This way column rulers and viewer widget would have access to wrapped or unwrapped model, whichever is more appropriate in that context. Most of the time wrapped model can be used.

Both of these strategies require modifying at least LineNumberRulerColumn.

As a proof of concept I wrote IDocumentAdapter that has additional IDocument that is wrapped and is used when returning line-based selections from the document. Experimenting with IDocumentAdapter showed that wrapping works (except some unregular input handling behaviour). But there seems to be no way to introduce wrapped document to the column rulers without modifying underlying file only by modifying IDocumentAdapter.

IDocumentInformationMapping might be a better place for wrapping model but as current implementation is pretty complex (as is probably the whole code folding support) then I would love to get some hints about the feasibility of extending this with word wrap.

Experiment with IDocumentAdapter

Creating IDocumentAdapter that supports word wrap would fix TextViewer to support wrapping. But as wrapping is only visual then all related ruler columns like annotations, line numberings etc fall apart.

Current AbstractDocumentAdapter wordwrap patch is providing visually wrapping viewer without any externally accessible wrapping model.

In addition JFaceTextUtil.modelLineToWidgetLine(...) should be patched so it's becoming responsible for returning same line number for multiple lines that form one wrapped line.

JFaceTextUtil.modelLineToWidgetLine(...):

public static int modelLineToWidgetLine(ITextViewer viewer, final int modelLine) {
	int widgetLine;
	if (viewer instanceof ITextViewerExtension5) {
		ITextViewerExtension5 extension= (ITextViewerExtension5) viewer;
		widgetLine= extension.modelLine2WidgetLine(modelLine);
	} else {
		IRegion region= viewer.getVisibleRegion();
		IDocument document= viewer.getDocument();
		try {
			int visibleStartLine= document.getLineOfOffset(region.getOffset());
			int visibleEndLine= document.getLineOfOffset(region.getOffset() + region.getLength());
			if (modelLine < visibleStartLine || modelLine > visibleEndLine)
				widgetLine= -1;
			else
			widgetLine= modelLine - visibleStartLine;
		} catch (BadLocationException x) {
			// ignore and return -1
			widgetLine= -1;
		}
	}
	return widgetLine;
}


StatusBar is showing cursor position correctly (2:34) even after patching DocumentAdapter to support word-wrap. Also Goto Line works correctly!

Patching DocumentAdapter seems to be not enough to keep editing functionality. Keyboard navigation is not fully functional (Del key does not work and some of the navigation has unexpected behaviour).

It would make sense that it's possible to wrap only visible area (not the whole IDocument). But looks like IDocumentAdapter cannot work this way

About LineNumberRulerColumn

LineNumberRulerColumn is currently using following to paint line numbers

for (int line= visibleLines.getStartLine(); line < lastLine; line++) {
	int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fCachedTextViewer, line);

	if (widgetLine == -1)
		continue;

	int lineHeight= fCachedTextWidget.getLineHeight(fCachedTextWidget.getOffsetAtLine(widgetLine));
	paintLine(line, y, lineHeight, gc, display);
	y += lineHeight;
}

new method in addition to modelLineToWidgetLine will be added to take wrapped line information from WrappedLineTracker. This logic could be patched by following to support word wrap:

int lastWidgetLine=-1;
int lastLineNumber = -1;
for (int line= visibleLines.getStartLine(); line < lastLine; line++) {
	int widgetLine= JFaceTextUtil.modelLineToWidgetLine(fCachedTextViewer, line);

	if (widgetLine == -1)
		continue;

	int lineHeight= fCachedTextWidget.getLineHeight(fCachedTextWidget.getOffsetAtLine(widgetLine));

	if (lastWidgetLine==widgetLine) {
		paintLine(lastLineNumber, y, lineHeight, gc, display);
	} else {
		paintLine(line, y, lineHeight, gc, display);
		lastLineNumber=line;
	}
	y += lineHeight;
}


Storing wrapped and unwrapped line number information about the same document

Both of these are required:

  • Wrapped line information is for the text viewer, code navigation, vertical rulers.
  • Unwrapped line information is required for functionalities like goto line, line numbering ruler column, current line highlight, saving file.

If document is modified then both models must be in sync. UPDATE: WrappingLineTracker that is based on TreeLineTracker is doing this.

Also it must be noted that changing editor size etc forces to rewrap the whole document and slows editor significiantly if the document is long.

If ILineTracker (implemented by WrappingLineTracker) is used for wrapping model then performance risks can be avoided. Line information in WrappingLineTracker is stored as a binary tree and adding additional nodes for each line (1..number of lines after wrapping this line) does not look like a big performance issue. But this can be sure after WrappingLineTracker is properly implemented.