/*
* DeepIndentRule.java
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2006 Matthieu Casanova
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.indent;
import org.gjt.sp.jedit.TextUtilities;
import org.gjt.sp.jedit.buffer.JEditBuffer;
import org.gjt.sp.jedit.syntax.Token;
import org.gjt.sp.jedit.syntax.TokenHandler;
import org.gjt.sp.jedit.syntax.TokenMarker;
import java.util.List;
import java.util.Stack;
import javax.swing.text.Segment;
/**
* Deep indent rule.
*
* @author Matthieu Casanova
* @version $Id: DeepIndentRule.java 21831 2012-06-18 22:54:17Z ezust $
*/
public class DeepIndentRule implements IndentRule
{
private final char openChar;
private final char closeChar;
//{{{ DeepIndentRule constructor
public DeepIndentRule(char openChar, char closeChar)
{
this.openChar = openChar;
this.closeChar = closeChar;
} //}}}
//{{{ apply() method
public void apply(JEditBuffer buffer, int thisLineIndex,
int prevLineIndex, int prevPrevLineIndex,
List<IndentAction> indentActions)
{
if (prevLineIndex == -1)
return;
int lineIndex = prevLineIndex;
int oldLineIndex = lineIndex;
CharSequence lineText = buffer.getLineSegment(lineIndex);
int searchPos = -1;
while (true)
{
if (lineIndex != oldLineIndex)
{
lineText = buffer.getLineSegment(lineIndex);
oldLineIndex = lineIndex;
}
Parens parens = new Parens(buffer, lineIndex, searchPos);
// No unmatched parens on prev line.
if (parens.openOffset == -1 && parens.closeOffset == -1)
{
// Try prev-prev line if present.
if (prevPrevLineIndex != -1) {
searchPos = -1;
lineIndex = prevPrevLineIndex;
prevPrevLineIndex = -1;
continue;
}
return;
}
// There's an unmatched open parenthesis - we want to
// align according to its position.
if (parens.closeOffset == -1)
{
// recalculate column (when using tabs instead of spaces)
int indent = parens.openOffset + getIndent(lineText, buffer.getTabSize()) - lineText.length();
indentActions.clear();
indentActions.add(new IndentAction.AlignParameter(indent));
return;
}
// There's an unmatched closed parenthesis - find the
// matching parenthesis and start looking from there again.
int openParenOffset = TextUtilities.findMatchingBracket(buffer, lineIndex, parens.closeOffset);
if (openParenOffset >= 0)
{
// Avoid falling back to the prev-prev line in this case.
prevPrevLineIndex = -1;
lineIndex = buffer.getLineOfOffset(openParenOffset);
searchPos = openParenOffset - buffer.getLineStartOffset(lineIndex) - 1;
if (searchPos < 0)
break;
}
else
break;
}
} //}}}
//{{{ getIndent() method
/**
* Returns the length of the string as if it were indented with
* spaces instead of tabs.
*/
private int getIndent(CharSequence line, int tabSize)
{
int cnt = 0;
for (int i = 0; i < line.length(); i++)
{
if (line.charAt(i) == '\t')
{
cnt += tabSize;
}
else
{
if (!Character.isWhitespace(line.charAt(i)))
{
cnt += (line.length() - i);
break;
}
cnt++;
}
}
return cnt;
} //}}}
//{{{ Parens class
/**
* A token filter that looks for the position of the open and
* close characters in the line being parsed. Characters inside
* literals and comments are ignored.
*/
private class Parens implements TokenHandler
{
int openOffset;
int closeOffset;
private int searchPos;
private Stack<Integer> open;
private Stack<Integer> close;
Parens(JEditBuffer b, int line, int pos)
{
this.searchPos = pos;
this.open = new Stack<Integer>();
this.close = new Stack<Integer>();
b.markTokens(line, this);
openOffset = (open.isEmpty()) ? -1 : open.pop();
closeOffset = (close.isEmpty()) ? -1 : close.pop();
}
public void handleToken(Segment seg,
byte id,
int offset,
int length,
TokenMarker.LineContext context)
{
if (length <= 0 ||
(searchPos != -1 && searchPos < offset))
{
return;
}
if (searchPos != -1 && offset + length > searchPos)
{
length = searchPos - offset + 1;
}
switch (id)
{
case Token.COMMENT1:
case Token.COMMENT2:
case Token.COMMENT3:
case Token.COMMENT4:
case Token.LITERAL1:
case Token.LITERAL2:
case Token.LITERAL3:
case Token.LITERAL4:
/* Ignore comments and literals. */
break;
default:
for (int i = offset; i < offset + length; i++)
{
if (seg.array[seg.offset + i] == openChar)
{
if (open.isEmpty() && !close.isEmpty())
close.pop();
else
open.push(i);
}
else if (seg.array[seg.offset + i] == closeChar)
{
if (close.isEmpty() && !open.isEmpty())
open.pop();
else
close.push(i);
}
}
break;
}
}
public void setLineContext(TokenMarker.LineContext lineContext)
{
/* Do nothing. */
}
@Override
public String toString()
{
return "Parens(" + openOffset + ',' + closeOffset + ')';
}
} //}}}
}