package ca.weblite.netbeans.mirah;
import ca.weblite.netbeans.mirah.lexer.MirahTokenId;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import javax.swing.text.Position;
import javax.swing.text.StyledDocument;
import mirah.impl.Tokens;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.netbeans.modules.editor.indent.spi.Context;
import org.netbeans.modules.editor.indent.spi.ExtraLock;
import org.netbeans.modules.editor.indent.spi.IndentTask;
import ca.weblite.netbeans.mirah.lexer.DocumentQuery;
/*
* To change this license header, choose License Headers in Project Properties.
* To change this template file, choose Tools | Templates
* and open the template in the editor.
*/
/**
*
* @author shannah
*/
public class MirahIndentTask implements IndentTask {
private Context context;
private static final Logger LOG =
Logger.getLogger(MirahIndentTask.class.getCanonicalName());
public MirahIndentTask(Context ctx){
this.context = ctx;
}
@Override
public void reindent() throws BadLocationException {
//System.out.println("Reindenting");
/*
1. Single Indent This Line
- Last line is def, if, else, elsif, begin, do, rescue, class,
interface
2. Double Indent This Line
- Last line is hanging def, if, elsif, rescue, class, or enterface
3. Single un-indent last line
- Last line was a finished double-indent from previous hanging def,
if, elsif, rescue, class or interface
- Last line was rescue, end, or ensure and was -- change indent to
match opener
4.
*/
if ( context.startOffset() <= 0 ){
return;
}
int indentSize = IndentUtils.indentLevelSize(context.document());
int prevLineStart = context.lineStartOffset(context.startOffset()-1);
int prevIndent = context.lineIndent(prevLineStart);
int currLineStart = context.lineStartOffset(context.startOffset());
int currLineEnd = currLineStart;
while ( currLineEnd < context.document().getLength() &&
!"\n".equals(context.document().getText(currLineEnd, 1))){
currLineEnd++;
}
int prevLineLen = currLineStart - prevLineStart-1;
if ( prevLineLen < 0){
prevLineLen = 0;
}
int prevLineEnd = prevLineStart+prevLineLen;
int currLineLen = currLineEnd-currLineStart;
if ( currLineLen < 0 ){
currLineLen = 0;
}
javax.swing.text.Element paragraph =
((StyledDocument)context.document()).
getParagraphElement(currLineStart);
javax.swing.text.Element lastParagraph =
((StyledDocument)context.document()).
getParagraphElement(prevLineStart);
int indent = prevIndent;
DocumentQuery dq = new DocumentQuery(context.document());
TokenSequence<MirahTokenId> seq = dq.getTokens(prevLineStart, false);
// First non-white token
MirahTokenId firstNonWhiteToken = null;
int firstNonWhiteOffset = -1;
while ( seq.offset() < prevLineEnd && seq.token() != null){
MirahTokenId curr = seq.token().id();
if ( !MirahTokenId.WHITESPACE_AND_COMMENTS.contains(curr)){
firstNonWhiteToken = curr;
firstNonWhiteOffset = seq.offset();
break;
}
seq.moveNext();
}
Set<MirahTokenId> conditionals = MirahTokenId.set(
Tokens.tIf,
Tokens.tElsif,
Tokens.tUnless,
Tokens.tWhile,
Tokens.tUnless
);
Set<MirahTokenId> blockBeginTokens = MirahTokenId.set(
Tokens.tClass,
Tokens.tInterface,
Tokens.tDef,
Tokens.tIf,
Tokens.tBegin,
Tokens.tBEGIN,
Tokens.tUnless,
Tokens.tWhile
);
Set<MirahTokenId> ifOrBegin = MirahTokenId.set(
Tokens.tIf,
Tokens.tUnless,
Tokens.tBegin,
Tokens.tBEGIN,
Tokens.tWhile
);
MirahTokenId lastIfOrBeginToken = null;
int lastIfOrBeginPos = -1;
int prevPos = seq.offset();
while ( seq.offset() < prevLineEnd && seq.token() != null){
MirahTokenId curr = seq.token().id();
if ( conditionals.contains(curr)){
lastIfOrBeginToken = curr;
lastIfOrBeginPos = seq.offset();
break;
}
seq.moveNext();
}
MirahTokenId lastIfOrBeginPrefixToken = null;
if ( lastIfOrBeginPos != -1 ){
seq.move(lastIfOrBeginPos);
while ( seq.movePrevious() && seq.offset() >= prevLineStart){
if ( !MirahTokenId.
WHITESPACE_AND_COMMENTS.contains(seq.token().id())){
lastIfOrBeginPrefixToken = seq.token().id();
break;
}
}
}
Set<MirahTokenId> assignmentTokens = MirahTokenId.set(
Tokens.tOpAssign,
Tokens.tOrEq,
Tokens.tAndEq,
Tokens.tPipes,
Tokens.tPlus,
Tokens.tMinus,
Tokens.tEEEQ,
Tokens.tEEQ,
Tokens.tGE,
Tokens.tLT,
Tokens.tIn,
Tokens.tGT,
Tokens.tLE,
Tokens.tAmpers,
Tokens.tNE,
Tokens.tQuestion,
Tokens.tEQ
);
if ( lastIfOrBeginPrefixToken != null &&
assignmentTokens.contains(lastIfOrBeginPrefixToken)){
firstNonWhiteToken = lastIfOrBeginToken;
seq.move(lastIfOrBeginPos-1);
seq.moveNext();
}
// If the first non-white token of the line is a "begin" token like "if"
// or "class", then we will do an indent on the next line, as long
// as the begin isn't ended on the same line.
if ( firstNonWhiteToken != null &&
blockBeginTokens.contains(firstNonWhiteToken)){
// This is a def
int parenMatch = 0;
int brackMatch = 0;
int braceMatch = 0;
int saveOffset = seq.offset();
MirahTokenId lastNonWhiteToken = null;
while ( seq.offset() < prevLineEnd && seq.token() != null ){
int ordinal = seq.token().id().ordinal();
if ( ordinal == Tokens.tLBrace.ordinal()){
braceMatch++;
} else if ( ordinal == Tokens.tLBrack.ordinal()){
brackMatch++;
} else if ( ordinal == Tokens.tLParen.ordinal()){
parenMatch++;
} else if ( ordinal == Tokens.tRBrace.ordinal()){
braceMatch--;
} else if ( ordinal == Tokens.tRBrack.ordinal()){
brackMatch--;
} else if ( ordinal == Tokens.tRParen.ordinal()){
parenMatch--;
}
if ( !MirahTokenId.WHITESPACE_AND_COMMENTS.
contains(seq.token().id())){
lastNonWhiteToken = seq.token().id();
}
seq.moveNext();
}
Set<MirahTokenId> continuers = MirahTokenId.CONTINUATION_TOKENS;
if ( parenMatch > 0 || brackMatch > 0 || braceMatch > 0 ||
continuers.contains(lastNonWhiteToken) ){
indent += indentSize*2;
} else {
indent += indentSize;
}
} else {
MirahTokenId lastNonWhite =
dq.lastNonWhiteTokenOfLine(prevLineStart);
Set<MirahTokenId> continuers = MirahTokenId.CONTINUATION_TOKENS;
if ( !continuers.contains(lastNonWhite)){
// The last line is not itself a continuation... check
// if the previous line was.
javax.swing.text.Element par =
((StyledDocument)context.document()).
getParagraphElement(prevLineStart-1);
javax.swing.text.Element rootPar = null;
while ( par != null ){
lastNonWhite =
dq.lastNonWhiteTokenOfLine(par.getStartOffset());
if ( continuers.contains(lastNonWhite)){
rootPar = par;
int newOff = par.getStartOffset()-1;
if ( newOff >=0 ){
par = ((StyledDocument)context.document()).
getParagraphElement(newOff);
} else {
par = null;
}
} else {
par = null;
}
}
if ( rootPar != null ){
// We found the root of the continuation.
int rootLineStart =
context.lineStartOffset(rootPar.getStartOffset());
int rootLineIndent =
context.lineIndent(rootLineStart);
MirahTokenId firstTokInRoot =
dq.firstNonWhiteToken(rootLineStart);
if ( blockBeginTokens.contains(firstTokInRoot)){
indent = rootLineIndent+indentSize;
} else {
indent = rootLineIndent;
}
}
} else {
// This line is a continuation
// Check and see if the previous line was also a continuation
// The last line is not itself a continuation... check
// if the previous line was.
javax.swing.text.Element par =
((StyledDocument)context.document()).
getParagraphElement(prevLineStart-1);
javax.swing.text.Element rootPar = null;
while ( par != null ){
lastNonWhite =
dq.lastNonWhiteTokenOfLine(par.getStartOffset());
if ( continuers.contains(lastNonWhite)){
rootPar = par;
int newOff = par.getStartOffset()-1;
if ( newOff >=0 ){
par = ((StyledDocument)context.document()).
getParagraphElement(newOff);
} else {
par = null;
}
} else {
par = null;
}
}
if ( rootPar == null ){
// The prev line is the original continuation
indent = prevIndent+indentSize;
}
}
}
Set<MirahTokenId> closers = MirahTokenId.set(
Tokens.tElse,
Tokens.tElsif,
Tokens.tWhen,
Tokens.tEnd,
Tokens.tRescue,
Tokens.tEnsure
);
seq = dq.getTokens(currLineStart, false);
firstNonWhiteToken = null;
while ( seq.offset() < currLineEnd ){
if ( !MirahTokenId.WHITESPACE_AND_COMMENTS.
contains(seq.token().id())){
firstNonWhiteToken = seq.token().id();
break;
}
seq.moveNext();
}
Set<MirahTokenId> ifChildren =
MirahTokenId.set(Tokens.tElsif, Tokens.tElse);
Set<MirahTokenId> exceptionTokens =
MirahTokenId.set(Tokens.tRescue, Tokens.tEnsure);
if ( firstNonWhiteToken != null &&
firstNonWhiteToken.ordinal() == Tokens.tEnd.ordinal()){
Set<MirahTokenId> begins = MirahTokenId.set(
Tokens.tDef,
Tokens.tIf,
Tokens.tClass,
Tokens.tInterface,
Tokens.tDo,
Tokens.tBegin,
Tokens.tUnless,
Tokens.tWhile
);
int balance = 0;
while ( seq.offset() > 0 && seq.token() != null ){
MirahTokenId tok = seq.token().id();
if ( tok.ordinal() == Tokens.tEnd.ordinal() ){
balance++;
} else if ( begins.contains(tok)){
balance--;
if ( balance <= 0 ){
int off = seq.offset();
//if ( off > 0 ) off--;
indent = context.lineIndent(
context.lineStartOffset(off)
);
break;
}
}
seq.movePrevious();
}
} else if ( firstNonWhiteToken != null &&
ifChildren.contains(firstNonWhiteToken)){
Set<MirahTokenId> begins = MirahTokenId.set(Tokens.tIf);
int balance = 0;
while ( seq.offset() > 0 && seq.token() != null ){
MirahTokenId tok = seq.token().id();
if ( tok.ordinal() == Tokens.tEnd.ordinal() ){
balance++;
} else if ( begins.contains(tok)){
balance--;
if ( balance < 0 ){
indent = context.lineIndent(seq.offset());
break;
}
}
seq.movePrevious();
}
} else if ( firstNonWhiteToken != null &&
exceptionTokens.contains(firstNonWhiteToken)){
Set<MirahTokenId> begins = MirahTokenId.set(
Tokens.tDef,
Tokens.tIf,
Tokens.tClass,
Tokens.tInterface,
Tokens.tDo,
Tokens.tBegin
);
int balance = 0;
while ( seq.offset() > 0 && seq.token() != null ){
MirahTokenId tok = seq.token().id();
if ( tok.ordinal() == Tokens.tEnd.ordinal() ){
balance++;
} else if ( begins.contains(tok)){
balance--;
if ( balance < 0 ){
indent = context.lineIndent(seq.offset());
break;
}
}
seq.movePrevious();
}
}
if ( indent >= 0 ){
context.modifyIndent(currLineStart, indent);
}
// Now let's modify the indentation of the previous line
MirahTokenId prevFirstNonWhite = dq.firstNonWhiteToken(prevLineStart);
Set<MirahTokenId> reorgs = MirahTokenId.set(
Tokens.tEnd,
Tokens.tRescue,
Tokens.tEnsure,
Tokens.tElse,
Tokens.tElsif
);
Set<MirahTokenId> ifEnders =
MirahTokenId.set(Tokens.tElse, Tokens.tElsif);
Set<MirahTokenId> exceptionEnds =
MirahTokenId.set(Tokens.tRescue, Tokens.tEnsure);
Set<MirahTokenId> ifUnlessTokens =
MirahTokenId.set(Tokens.tIf, Tokens.tUnless);
boolean changePrevLineIndent = false;
int changePrevLineIndentTo = 0;
if ( prevFirstNonWhite != null &&
prevFirstNonWhite.ordinal() == Tokens.tEnd.ordinal()){
// Let's find the matching line
int startOff = prevLineStart;
if ( startOff > 0 ) startOff--;
seq = dq.getTokens(startOff, false);
Set<MirahTokenId> openers = MirahTokenId.set(
Tokens.tDo,
Tokens.tIf,
Tokens.tDef,
Tokens.tBegin,
Tokens.tClass,
Tokens.tInterface,
Tokens.tUnless
);
int balance = 0;
while ( seq.token() != null && seq.offset() > 0 ){
int ord = seq.token().id().ordinal();
if ( ord == Tokens.tEnd.ordinal()){
balance++;
} else if ( openers.contains(seq.token().id())){
if ( ord == Tokens.tIf.ordinal() ){
// If statements don't necessarily have a close block..
// only if the "if" is the first nonwhite of the line
MirahTokenId firstNonWhiteOfIfLine =
dq.firstNonWhiteToken(
context.lineStartOffset(seq.offset())
);
if ( firstNonWhiteOfIfLine != null &&
firstNonWhiteOfIfLine.ordinal() ==
Tokens.tIf.ordinal() ){
balance--;
} else {
// See if the if is prefixed
int offset = seq.offset();
int bol = context.lineStartOffset(offset);
MirahTokenId ifPrefixToken = null;
while ( seq.movePrevious() && seq.offset() > bol ){
if ( !MirahTokenId.WHITESPACE_AND_COMMENTS.
contains(seq.token().id())){
ifPrefixToken = seq.token().id();
break;
}
}
seq.move(offset+1);
seq.movePrevious();
if ( ifPrefixToken != null &&
assignmentTokens.contains(ifPrefixToken)){
balance--;
}
}
} else {
balance--;
}
if ( balance < 0 ){
// We found the line that we need to match it with
changePrevLineIndentTo =
context.lineIndent(
context.lineStartOffset(seq.offset())
);
changePrevLineIndent = true;
break;
}
}
seq.movePrevious();
}
} else if ( prevFirstNonWhite != null &&
ifEnders.contains(prevFirstNonWhite) ){
int startOff = prevLineStart;
if ( startOff > 0 ) startOff--;
seq = dq.getTokens(startOff, false);
Set<MirahTokenId> openers = MirahTokenId.set(Tokens.tIf, Tokens.tUnless);
int balance = 0;
while ( seq.token() != null && seq.offset() > 0 ){
int ord = seq.token().id().ordinal();
if ( ord == Tokens.tEnd.ordinal()){
balance++;
} else if ( openers.contains(seq.token().id())){
//if ( ord == Tokens.tIf.ordinal() ){
// If statements don't necessarily have a close block..
// only if the "if" is the first nonwhite of the line
MirahTokenId firstNonWhiteOfIfLine =
dq.firstNonWhiteToken(
context.lineStartOffset(seq.offset())
);
if ( firstNonWhiteOfIfLine != null &&
openers.contains(firstNonWhiteOfIfLine) ){
balance--;
} else {
// See if the if is prefixed
int offset = seq.offset();
int bol = context.lineStartOffset(offset);
MirahTokenId ifPrefixToken = null;
while ( seq.movePrevious() && seq.offset() > bol ){
if ( !MirahTokenId.WHITESPACE_AND_COMMENTS.
contains(seq.token().id())){
ifPrefixToken = seq.token().id();
break;
}
}
seq.move(offset+1);
seq.movePrevious();
if ( ifPrefixToken != null &&
assignmentTokens.contains(ifPrefixToken)){
balance--;
}
}
//} else {
// balance--;
//}
if ( balance < 0 ){
// We found the line that we need to match it with
changePrevLineIndentTo =
context.lineIndent(
context.lineStartOffset(seq.offset())
);
changePrevLineIndent = true;
break;
}
}
seq.movePrevious();
}
} else if ( prevFirstNonWhite != null &&
exceptionEnds.contains(prevFirstNonWhite)){
int startOff = prevLineStart;
if ( startOff > 0 ) startOff--;
seq = dq.getTokens(startOff, false);
Set<MirahTokenId> openers = MirahTokenId.set(Tokens.tBegin);
int balance = 0;
while ( seq.token() != null && seq.offset() > 0 ){
int ord = seq.token().id().ordinal();
if ( ord == Tokens.tEnd.ordinal()){
balance++;
} else if ( openers.contains(seq.token().id())){
//if ( ord == Tokens.tIf.ordinal() ){
// If statements don't necessarily have a close block..
// only if the "if" is the first nonwhite of the line
MirahTokenId firstNonWhiteOfIfLine =
dq.firstNonWhiteToken(
context.lineStartOffset(seq.offset())
);
if ( firstNonWhiteOfIfLine != null &&
openers.contains(firstNonWhiteOfIfLine) ){
balance--;
} else {
// See if the if is prefixed
int offset = seq.offset();
int bol = context.lineStartOffset(offset);
MirahTokenId ifPrefixToken = null;
while ( seq.movePrevious() && seq.offset() > bol ){
if ( !MirahTokenId.WHITESPACE_AND_COMMENTS.
contains(seq.token().id())){
ifPrefixToken = seq.token().id();
break;
}
}
seq.move(offset+1);
seq.movePrevious();
if ( ifPrefixToken != null &&
assignmentTokens.contains(ifPrefixToken)){
balance--;
}
}
//} else {
// balance--;
//}
if ( balance < 0 ){
// We found the line that we need to match it with
changePrevLineIndentTo = context.lineIndent(
context.lineStartOffset(seq.offset())
);
changePrevLineIndent = true;
break;
}
}
seq.movePrevious();
}
}
if ( changePrevLineIndent ){
context.modifyIndent(prevLineStart, changePrevLineIndentTo);
}
}
public void reindent_old() throws BadLocationException {
if ( context.startOffset() <= 0 ){
return;
}
int indentSize = IndentUtils.indentLevelSize(context.document());
int prevLineStart = context.lineStartOffset(context.startOffset()-1);
int prevIndent = context.lineIndent(prevLineStart);
int currLineStart = context.lineStartOffset(context.startOffset());
int currLineEnd = currLineStart;
while ( currLineEnd < context.document().getLength() &&
!"\n".equals(context.document().getText(currLineEnd, 1))){
currLineEnd++;
}
int prevLineLen = currLineStart - prevLineStart-1;
if ( prevLineLen < 0){
prevLineLen = 0;
}
int currLineLen = currLineEnd-currLineStart;
if ( currLineLen < 0 ){
currLineLen = 0;
}
// Two questions:
// 1. Do we need to adjust the indent of the previous line.
// 2. Do we need to adjust the indent of the current line.
TokenSequence<MirahTokenId> toks = mirahTokenSequence(
context.document(),
context.caretOffset(),
true
);
// Check previous line for else
MirahTokenId tElse = MirahTokenId.get(Tokens.tElse.ordinal());
MirahTokenId tElsIf = MirahTokenId.get(Tokens.tElsif.ordinal());
MirahTokenId tIf = MirahTokenId.get(Tokens.tIf.ordinal());
//int index = toks.index();
int changePrevIndent = -1;
while ( toks.offset() > prevLineStart ){
Token<MirahTokenId> curr = toks.token();
if ( curr.id() == tElse || curr.id() == tElsIf ){
// We have an else... find the matching if
Token<MirahTokenId> curr2 = toks.token();
while ( tIf != curr2.id() && toks.movePrevious() ){
curr2 = toks.token();
}
if ( tIf == curr2.id() ){
int ifStartLineOffset =
context.lineStartOffset(toks.offset());
changePrevIndent = context.lineIndent(ifStartLineOffset);
break;
}
}
if ( !toks.movePrevious() ){
break;
}
}
Position prevLineStartPos =
context.document().createPosition(prevLineStart);
Position currLineStartPos =
context.document().createPosition(currLineStart);
Position currLineEndPos =
context.document().createPosition(currLineEnd);
prevIndent = context.lineIndent(prevLineStartPos.getOffset());
if ( changePrevIndent >= 0 && prevIndent != changePrevIndent){
context.modifyIndent(
prevLineStartPos.getOffset(),
changePrevIndent
);
}
prevLineStart = prevLineStartPos.getOffset();
currLineStart = currLineStartPos.getOffset();
currLineEnd = currLineEndPos.getOffset();
prevLineLen = currLineStart - prevLineStart-1;
if ( prevLineLen < 0){
prevLineLen = 0;
}
currLineLen = currLineEnd-currLineStart;
if ( currLineLen < 0 ){
currLineLen = 0;
}
int indent = prevIndent;
MirahTokenId tEnd = MirahTokenId.get(Tokens.tEnd.ordinal());
Set<MirahTokenId> openers = new HashSet<MirahTokenId>();
openers.add(MirahTokenId.get(Tokens.tClass.ordinal()));
openers.add(MirahTokenId.get(Tokens.tBegin.ordinal()));
openers.add(MirahTokenId.get(Tokens.tIf.ordinal()));
openers.add(MirahTokenId.get(Tokens.tDo.ordinal()));
openers.add(MirahTokenId.get(Tokens.tDef.ordinal()));
openers.add(MirahTokenId.get(Tokens.tInterface.ordinal()));
LinkedList<MirahTokenId> foundOpeners = new LinkedList<MirahTokenId>();
boolean equalsEncountered = false;
//int numOpeners = 0;
toks = mirahTokenSequence(context.document(), prevLineStart, false);
while ( toks.offset() < currLineStart ){
Token<MirahTokenId> tok = toks.token();
if ( tok.id().ordinal() == Tokens.tEQ.ordinal()){
equalsEncountered = true;
}
if ( tok.id() == tEnd ){
//numOpeners--;
if ( !foundOpeners.isEmpty()){
foundOpeners.pop();
}
} else if ( openers.contains(tok.id()) &&
!(tok.id().ordinal() == Tokens.tIf.ordinal() &&
equalsEncountered)
){
foundOpeners.push(tok.id());
}
toks.moveNext();
}
if ( !foundOpeners.isEmpty()){
indent += foundOpeners.size()*indentSize;
}
toks = mirahTokenSequence(
context.document(),
context.caretOffset(),
false
);
Set<MirahTokenId> closers = new HashSet<MirahTokenId>();
closers.add(tEnd);
closers.add(MirahTokenId.get(Tokens.tElse.ordinal()));
closers.add(MirahTokenId.get(Tokens.tElsif.ordinal()));
closers.add(MirahTokenId.get(Tokens.tWhen.ordinal()));
toks.move(currLineStart);
toks.moveNext();
while ( toks.offset() < currLineEnd ){
if ( closers.contains(toks.token().id())){
indent -= indentSize;
break;
}
toks.moveNext();
}
if ( indent >= 0 ){
context.modifyIndent(currLineStart, indent);
}
}
/**
* Get token sequence positioned over a token.
*
* @param doc
* @param caretOffset
* @param backwardBias
* @return token sequence positioned over a token that "contains" the offset
* or null if the document does not contain any java token sequence or the
* offset is at doc-or-section-start-and-bwd-bias or
* doc-or-section-end-and-fwd-bias.
*/
private static TokenSequence<MirahTokenId> mirahTokenSequence(
Document doc,
int caretOffset,
boolean backwardBias) {
TokenHierarchy<?> hi = TokenHierarchy.get(doc);
List<TokenSequence<?>> tsList =
hi.embeddedTokenSequences(caretOffset, backwardBias);
// Go from inner to outer TSes
for (int i = tsList.size() - 1; i >= 0; i--) {
TokenSequence<?> ts = tsList.get(i);
if (ts.languagePath().innerLanguage() ==
MirahTokenId.getLanguage()) {
TokenSequence<MirahTokenId> javaInnerTS =
(TokenSequence<MirahTokenId>) ts;
return javaInnerTS;
}
}
return null;
}
@Override
public ExtraLock indentLock() {
return null;
}
}