package bluej.editor.moe;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import bluej.Config;
import bluej.editor.moe.MoeSyntaxDocument.Element;
import bluej.editor.moe.MoeSyntaxDocument.Position;
import bluej.parser.nodes.NodeTree.NodeAndPosition;
import bluej.parser.nodes.ParsedNode;
| This class contains the "auto layout" functionality of the editor.
|*/
public class MoeIndent{
// from beginning string match any number of whitespaces or tabs.
private static final Pattern WHITESPACE_TABS = Pattern.compile("^[ \\t]*");
// from beginning of string match any number of whitespaces, tabs
// and stars, followed by an optional forward slash and star.
private static final Pattern WHITESPACE_TABS_STAR = Pattern.compile("^[ \\t*]*(/\\*)?");
public static class AutoIndentInformation
{
private boolean perfect;
private int newCaretPos;
public AutoIndentInformation(boolean perfect, int newCaretPos)
|
|{
|
|this.perfect = perfect;
|
|this.newCaretPos = newCaretPos;
|
|}
|
|public boolean isPerfect()
|
|{
|
|return perfect;
|
|}
|
|public int getNewCaretPosition()
|
|{
|
|return newCaretPos;
|
|}
|
|}
|
|/**
| Perform an auto-layout - calculate the correct indent for each source line, and apply it. Return
| information about the applied indentation.
|
public static AutoIndentInformation calculateIndentsAndApply(MoeSyntaxDocument doc, int caretPos)
{
return calculateIndentsAndApply(doc, 0, doc.getLength(), caretPos);
}
| Perform an auto-layout - calculate the correct indent for each source line between the given
| start and end positions, and apply it. Return information about the applied indentation.
|
public static AutoIndentInformation calculateIndentsAndApply(MoeSyntaxDocument doc,
int startPos, int endPos, int prevCaretPos)
{
int caretPos = prevCaretPos;
Element rootElement = doc.getDefaultRootElement();
List<DocumentAction> methodUpdates = new LinkedList<DocumentAction>();
List<DocumentAction> updates = new ArrayList<DocumentAction>(rootElement.getElementCount());
IndentCalculator ii = new RootIndentCalculator();
boolean lastLineWasBlank = false;
boolean perfect = true;
NodeAndPosition<ParsedNode> root = new NodeAndPosition<ParsedNode>(doc.getParser(), 0, doc.getParser().getSize());
Position startp, endp;
startp = doc.createPosition(startPos);
endp = doc.createPosition(endPos);
checkMethodSpacing(root, rootElement, methodUpdates, startPos, endPos);
for (DocumentAction methodUpdate : methodUpdates) {
caretPos = methodUpdate.apply(doc, caretPos);
}
methodUpdates = null;
for (int i = 0; i < rootElement.getElementCount(); i++) {
Element el = rootElement.getElement(i);
if (el.getEndOffset() > startp.getOffset() && el.getStartOffset() < endp.getOffset()) {
boolean thisLineBlank = isWhiteSpaceOnly(getElementContents(doc, el));
if (thisLineBlank) {
if (caretPos >= el.getStartOffset() && caretPos < el.getEndOffset()) {
caretPos = el.getStartOffset();
}
if (lastLineWasBlank) {
if (el.getEndOffset() <= doc.getLength()) {
doc.remove(el.getStartOffset(), el.getEndOffset() - el.getStartOffset());
perfect = false;
}
}
else {
int rmlen = el.getEndOffset() - el.getStartOffset() - 1;
if (rmlen > 0) {
doc.remove(el.getStartOffset(), rmlen);
}
}
}
lastLineWasBlank = thisLineBlank;
}
}
doc.flushReparseQueue();
for (int i = 0; i < rootElement.getElementCount(); i++) {
Element el = rootElement.getElement(i);
if (el.getEndOffset() > startp.getOffset() && el.getStartOffset() < endp.getOffset()) {
boolean thisLineBlank = isWhiteSpaceOnly(getElementContents(doc, el));
DocumentAction update = null;
if (!thisLineBlank) {
String indent = calculateIndent(el, root, ii, doc);
update = new DocumentIndentAction(i, indent);
perfect = perfect && getElementContents(doc, el).startsWith(indent)
&& !isWhiteSpaceOnly(getElementContents(doc, el).substring(indent.length(),indent.length() + 1));
}
if (update != null) {
updates.add(update);
}
lastLineWasBlank = thisLineBlank;
}
}
for (DocumentAction update : updates) {
caretPos = update.apply(doc, caretPos);
}
startp.dispose();
endp.dispose();
return new AutoIndentInformation(perfect, caretPos);
}
| Finds the indent for the given element by looking at the nodes in the parse tree
|
| @param el The element to calculate the indent for
| @param start The Node that is either the one directly containing the given element,
| or is an ancestor of the one that directly contains the given element,
| or may not contain the element at all (in which case null will be returned)
| @param startIC The IndentCalculator corresponding to start
| @param doc The document involved
| @return The indent that the element should have, up to the first non-whitespace character.
| Returns null if start does not contain the given element
|
private static String calculateIndent(Element el,
NodeAndPosition<ParsedNode> start, IndentCalculator startIC, MoeSyntaxDocument doc)
{
int pos = el.getStartOffset() + findFirstNonIndentChar(getElementContents(doc, el), true);
if (pos >= start.getPosition() && pos < start.getEnd()) {
for (Iterator<NodeAndPosition<ParsedNode>> i = start.getNode().getChildren(start.getPosition()); i.hasNext();) {
NodeAndPosition<ParsedNode> nap = i.next();
String inner = calculateIndent(el, nap, startIC.getForChild(nap.getNode()), doc);
if (inner != null) {
return inner;
}
}
return startIC.getCurIndent(doc.getText(pos, 1).charAt(0));
}
else {
return null;
}
}
| Loops through the children of the specified {}link root} to look for methods
| that have no space between them, then recursively looks at the children
| to see if they have any inner methods.
|
| <p>When it does identify two methods with no gap in between them it adds
| a new {}link DocumentAddLineAction} object with the current position
| to the {}link updates} list.
| @param root Node to look inside of.
| @param map Map of the document used to get the lines of the method.
| @param updates List to update with new actions where needed.
| @param startPos Start of document region to scan
| @param endPos End of document region to scan
|
private static void checkMethodSpacing(NodeAndPosition<ParsedNode> root, Element map,
List<DocumentAction> updates, int startPos, int endPos)
{
NodeAndPosition<ParsedNode> current = null;
NodeAndPosition<ParsedNode> next = null;
for (Iterator<NodeAndPosition<ParsedNode>> i = root.getNode().getChildren(root.getPosition()); i.hasNext();) {
next = i.next();
if (current != null &&
current.getNode().getNodeType() == ParsedNode.NODETYPE_METHODDEF &&
current.getNode().getNodeType() == next.getNode().getNodeType()) {
int currentLine = map.getElementIndex(current.getEnd() - 1);
int nextLine = map.getElementIndex(next.getPosition());
if (next.getPosition() >= startPos && next.getPosition() <= endPos) {
if ((currentLine + 1) == nextLine) {
updates.add(0, new DocumentAddLineAction(next.getPosition()));
}
else if ((currentLine == nextLine)) {
updates.add(0, new DocumentAddLineAction(next.getPosition(), true));
}
}
else if (current.getEnd() >= startPos && current.getEnd() <= endPos) {
if ((currentLine + 1) == nextLine) {
updates.add(0, new DocumentAddLineAction(current.getEnd()));
}
else if ((currentLine == nextLine)) {
updates.add(0, new DocumentAddLineAction(current.getEnd(), true));
}
}
}
current = next;
if (current.getPosition() > endPos) {
return;
}
checkMethodSpacing(current, map, updates, startPos, endPos);
}
}
| An interface that calculates the indentation level that
| the corresponding node should have. You should use getForChild as you
| descend the parse tree to get the indentation for child nodes.
|
private static interface IndentCalculator
{
| Gets the IndentCalculator for the given child node of the node that this
| IndentCalculator instance corresponds to
|
public IndentCalculator getForChild(ParsedNode n);
| Gets the indent for a line in the current node that begins with the
| given character. This allows for comments (such as this one right here)
| to have their leading asterisks indented by an extra space.
|
public String getCurIndent(char beginsWith);
}
| An implementation of IndentCalculator for the root node of the document.
|
private static class RootIndentCalculator
implements IndentCalculator
{
public IndentCalculator getForChild(ParsedNode n)
{
return new NodeIndentCalculator("", n);
}
public String getCurIndent(char beginsWith)
{
return "";
}
}
| An implementation of IndentCalculator for a non-root node of the document.
|
private static class NodeIndentCalculator
implements IndentCalculator
{
private final String existingIndent;
private final ParsedNode parent;
private static final int tabSize = Config.getPropInteger("bluej.editor.tabsize", 4);
private static final String spaces =
" ";
private final static String STANDARD_INDENT = spaces.substring(0, tabSize);
private final static String CONTINUATION_INDENT = STANDARD_INDENT;
private final static String COMMENT_ASTERISK_INDENT = " ";
public NodeIndentCalculator(String existingIndent, ParsedNode parent)
{
this.existingIndent = existingIndent;
this.parent = parent;
}
public IndentCalculator getForChild(ParsedNode child)
{
String newIndent = existingIndent;
if (child.isInner()) {
newIndent += STANDARD_INDENT;
}
else if (! child.isContainer() && ! parent.isContainer() && ! parent.isInner()) {
newIndent += CONTINUATION_INDENT;
}
return new NodeIndentCalculator(newIndent, child);
}
public String getCurIndent(char beginsWith)
{
if (parent.getNodeType() == ParsedNode.NODETYPE_COMMENT && beginsWith == '*') {
return existingIndent + COMMENT_ASTERISK_INDENT;
}
else {
return existingIndent;
}
}
}
| Interface representing some document editing action.
|
private interface DocumentAction
{
| Apply the edit represented by this DocumentAction to the document, and return the
| adjusted caret position.
|
public int apply(MoeSyntaxDocument doc, int prevCaretPos);
}
| A class representing an update to the indentation on a line of the document. This is different
| to a LineAction because it intrinsically knows which line it needs to update
|
private static class DocumentIndentAction
implements DocumentAction
{
private final int lineIndex;
private final String indent;
public DocumentIndentAction(int lineIndex, String indent)
{
this.lineIndex = lineIndex;
this.indent = indent;
}
public int apply(MoeSyntaxDocument doc, int caretPos)
{
Element el = doc.getDefaultRootElement().getElement(lineIndex);
String line = getElementContents(doc, el);
int lengthPrevWhitespace = findFirstNonIndentChar(line, true);
boolean anyTabs = line.substring(0, lengthPrevWhitespace).indexOf("\t") != -1;
if (indent != null && (anyTabs || (indent.length() != lengthPrevWhitespace))) {
int origStartOffset = el.getStartOffset();
doc.replace(el.getStartOffset(), lengthPrevWhitespace,
indent);
if (caretPos < origStartOffset) {
return caretPos;
}
else if (caretPos >= origStartOffset + lengthPrevWhitespace) {
int changeLength = indent.length() - lengthPrevWhitespace;
return caretPos + changeLength;
}
else {
return origStartOffset + indent.length();
}
}
else {
return caretPos;
}
}
}
| Get the textual contents of a document element (i.e. a line).
|
private static String getElementContents(MoeSyntaxDocument doc, Element el)
{
return doc.getText(el.getStartOffset(), el.getEndOffset() - el.getStartOffset());
}
| Return true if s contains only whitespace (or nothing).
|
public static boolean isWhiteSpaceOnly(String s)
{
return s.trim().length() == 0;
}
| Find the position of the first non-indentation character in a string.
| Indentation characters are [whitespace], //, *, /*, /**.
|
public static int findFirstNonIndentChar(String line, boolean whitespaceOnly)
{
Matcher m = whitespaceOnly ? WHITESPACE_TABS.matcher(line) : WHITESPACE_TABS_STAR.matcher(line);
return m.find() ? m.end() : 0;
}
| A document action for inserting a blank line in the document.
|
private static class DocumentAddLineAction
implements DocumentAction
{
private int position;
private boolean twoSeparators;
public DocumentAddLineAction(int position)
{
this(position, false);
}
public DocumentAddLineAction(int position, boolean twoSeparators)
{
this.position = position;
this.twoSeparators = twoSeparators;
}
| Tries to insert a new line into the document at the stated position.
| @param doc Document to add the new line to.
| @param prevCaretPos Location to move the the cursor to after the operation
| @return The caret position.
|
public int apply(MoeSyntaxDocument doc, int prevCaretPos)
{
String lineSeparator = System.getProperty("line.separator");
if (twoSeparators) {
doc.insertString(position, lineSeparator + lineSeparator);
}
else {
doc.insertString(position, lineSeparator);
}
if (position > prevCaretPos) {
return prevCaretPos;
}
else if (twoSeparators) {
return prevCaretPos + (lineSeparator.length() * 2);
}
else {
return prevCaretPos + lineSeparator.length();
}
}
}
}
. calculateIndentsAndApply
. calculateIndentsAndApply
. calculateIndent
. checkMethodSpacing
top,
use,
map,
interface IndentCalculator
. getForChild
. getCurIndent
top,
use,
map,
class RootIndentCalculator
. getForChild
. getCurIndent
top,
use,
map,
class NodeIndentCalculator
. NodeIndentCalculator
. getForChild
. getCurIndent
top,
use,
map,
interface DocumentAction
. apply
top,
use,
map,
class DocumentIndentAction
. DocumentIndentAction
. apply
. getElementContents
. isWhiteSpaceOnly
. findFirstNonIndentChar
top,
use,
map,
class DocumentAddLineAction
. DocumentAddLineAction
. DocumentAddLineAction
. apply
486 neLoCode
+ 63 LoComm