This tutorial describes how to provide indentation and reformatting capabilities for the NetBeans editor.
The indentation engine is represented by
IndentEngine class
It has three abstract methods that need to be implemented:
public abstract int indentLine(Document doc, int offset);
public abstract int indentNewLine(Document doc, int offset);
public abstract Writer createWriter(Document doc, int offset, Writer writer);
They allow to reindent the current line, find an indentation after newline
and indent a block of code that is intended to be inserted at a particular
offset in the document.
The indentation engine may either be written in a language-independent way or it may be specific for a language such as java or html.
A simplest implementation may look like IndentEngine.Default:
package myindent;
public class MyIndentEngine extends IndentEngine {
public int indentLine(Document doc, int offset) {
return offset;
}
public int indentNewLine(Document doc, int offset) {
try {
doc.insertString(offset, "\n", null); // NOI18N
offset++;
} catch (BadLocationException ex) {
// Invalid offset or document not mutable
}
return offset;
}
public Writer createWriter(Document doc, int offset, Writer writer) {
// Return an original writer which means
// no reindentation of the original text
return writer;
}
protected boolean acceptMimeType(String mime) {
return true; // Accepts any mime-type
}
}
In order to allow the created indentation engine to be used it is necessary to register the indentation engine as a service through the xml layer and create a corresponding beaninfo.
The layer for the module containing the indentation engine needs to be extended in the following way:
<filesystem>
<folder name="Services">
<folder name="IndentEngine">
<file name="myindent-MyIndentEngine.settings" url="MyIndentEngine.settings">
<attr name="SystemFileSystem.localizingBundle" stringvalue="org.netbeans.modules.mymodule.Bundle"/>
<attr name="SystemFileSystem.icon" urlvalue="nbresloc:/org/netbeans/modules/editor/resources/indentEngine.gif"/>
</file>
</folder>
</folder>
</filesystem>
The MyIndentEngine.settings file is expected to be in the same folder where layer.xml resides. It may look like this:
<?xml version="1.0"?>
<!DOCTYPE settings PUBLIC "-//NetBeans//DTD Session settings 1.0//EN" "http://www.netbeans.org/dtds/sessionsettings-1_0.dtd">
<settings version="1.0">
<module name="org.netbeans.modules.mymodule/1"/>
<instanceof class="org.openide.ServiceType"/>
<instanceof class="org.openide.text.IndentEngine"/>
<instanceof class="myindent.MyIndentEngine"/>
<instance class="myindent.MyIndentEngine"/>
</settings>
An example of simple beaninfo:
package myindent;
import java.beans.BeanDescriptor;
import java.beans.SimpleBeanInfo;
import java.util.ResourceBundle;
import org.openide.util.NbBundle;
/**
* Beaninfo for MyIndentEngine.
*
* @author Miloslav Metelka
*/
public class MyIndentEngineBeanInfo extends SimpleBeanInfo {
private BeanDescriptor beanDescriptor;
public MyIndentEngineBeanInfo() {
}
public BeanDescriptor getBeanDescriptor () {
if (beanDescriptor {{}} null) {
beanDescriptor = new BeanDescriptor(MyIndentEngine.class);
ResourceBundle bundle = NbBundle.getBundle(MyIndentEngine.class);
beanDescriptor.setDisplayName(bundle.getString("LAB_MyIndentEngine"));
beanDescriptor.setShortDescription(bundle.getString("HINT_MyIndentEngine"));
beanDescriptor.setValue("global", Boolean.TRUE); // NOI18N
}
return beanDescriptor;
}
}
A corresponding Bundle.properties:
LAB_MyIndentEngine=My Indent Engine HINT_MyIndentEngine=My special indentation engine
A more complex example of the indentNewLine() is a code snippet showing insertion of indent corresponding to the indent of the first non-empty (and non-whitespace only) line before the cursor:
public int indentNewLine(Document doc, int offset) {
try {
Element rootElem = doc.getDefaultRootElement();
// Offset should be valid -> no check for lineIndex -1
int lineIndex = rootElem.getElementIndex(offset);
String lineText;
int whitespaceEndIndex;
do {
Element lineElem = rootElem.getElement(lineIndex);
lineText = doc.getText(lineElem.getStartOffset(),
lineElem.getEndOffset() - lineElem.getStartOffset() - 1); // strip ending '\n'
whitespaceEndIndex = 0;
while (whitespaceEndIndex < lineText.length()) {
// Break on non-whitespace char
if (!Character.isWhitespace(lineText.charAt(whitespaceEndIndex))) {
lineIndex = 0; // stop outer loop
break;
}
whitespaceEndIndex++;
}
lineIndex--; // continue to search for previous non-whitespace line
} while (lineIndex >= 0);
String nlPlusIndent = "\n" + lineText.substring(0, whitespaceEndIndex);
doc.insertString(offset, nlPlusIndent, null); // NOI18N
offset += nlPlusIndent.length();
} catch (BadLocationException ex) {
// ignore
}
return offset;
}
The following implementation of createWriter() shows how to write a wrapping indentation writer that adds four extra spaces to each line:
public Writer createWriter(Document doc, int offset, Writer writer) {
return new IndentWriter(writer);
}
private static final class IndentWriter extends Writer {
private final Writer writer;
IndentWriter(Writer writer) {
this.writer = writer;
}
public void write(char[] cbuf, int off, int len) throws IOException {
int lastOff = off;
while (off < len) {
if (cbuf[off++] {{}} '\n') {
writer.write(cbuf, lastOff, off - lastOff);
lastOff = off;
// Write extra indent
writer.write(" ");
}
}
}
public void flush() throws IOException {
writer.flush();
}
public void close() throws IOException {
writer.close();
}
}
Indentation engine may be specialized to an indentation of a specific language. %BR% Such indentation language should extend FormatterIndentEngine class which only requires a single method to be implemented:
protected abstract ExtFormatter createFormatter();
ExtFormatter does the formatting by modifying a chain of TokenItem instances.