/*
* LineManager.java - Manages line info, line start offsets, positions
* :tabSize=4:indentSize=4:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 2001, 2004 Slava Pestov
*
* 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.buffer;
//{{{ Imports
import org.gjt.sp.jedit.syntax.*;
import org.gjt.sp.jedit.Debug;
import org.gjt.sp.util.IntegerArray;
import org.gjt.sp.util.Log;
//}}}
/**
* A class internal to jEdit's document model. You should not use it
* directly. To improve performance, none of the methods in this class
* check for out of bounds access, nor are they thread-safe. The
* <code>Buffer</code> class, through which these methods must be
* called through, implements such protection.
*
* @author Slava Pestov
* @version $Id: LineManager.java 23220 2013-09-29 19:19:24Z shlomy $
* @since jEdit 4.2pre3
*/
public class LineManager
{
//{{{ LineManager constructor
public LineManager()
{
endOffsets = new int[1];
endOffsets[0] = 1;
foldLevels = new short[1];
lineContext = new TokenMarker.LineContext[1];
lineCount = 1;
} //}}}
//{{{ getLineCount() method
public final int getLineCount()
{
return lineCount;
} //}}}
//{{{ getLineOfOffset() method
public int getLineOfOffset(int offset)
{
/*
* Performance optimization: assumption is that this method
* is called many times for the same line.
* Not optimizing the first line is intentional,
* profiling proves this is the right approach (#3528212).
*/
if(getLineOfOffsetLine > 0 && getLineOfOffsetLine < lineCount)
{
int s = getLineEndOffset(getLineOfOffsetLine - 1);
int e = getLineEndOffset(getLineOfOffsetLine);
if(offset >= s && offset < e)
return getLineOfOffsetLine;
}
int start = 0;
int end = lineCount - 1;
for(;;)
{
switch(end - start)
{
case 0:
if(getLineEndOffset(start) <= offset)
getLineOfOffsetLine = start + 1;
else
getLineOfOffsetLine = start;
return getLineOfOffsetLine;
case 1:
if(getLineEndOffset(start) <= offset)
{
if(getLineEndOffset(end) <= offset)
getLineOfOffsetLine = end + 1;
else
getLineOfOffsetLine = end;
}
else
getLineOfOffsetLine = start;
return getLineOfOffsetLine;
default:
int pivot = (end + start) / 2;
int value = getLineEndOffset(pivot);
if(value == offset)
{
getLineOfOffsetLine = pivot + 1;
return getLineOfOffsetLine;
}
else if(value < offset)
start = pivot + 1;
else
end = pivot - 1;
break;
}
}
} //}}}
//{{{ getLineEndOffset() method
public final int getLineEndOffset(int line)
{
if(gapLine != -1 && line >= gapLine)
return endOffsets[line] + gapWidth;
else
return endOffsets[line];
} //}}}
//{{{ getFoldLevel() method
public final int getFoldLevel(int line)
{
return foldLevels[line];
} //}}}
//{{{ setFoldLevel() method
// Also sets 'fold level valid' flag
public final void setFoldLevel(int line, int level)
{
if(level > 0xffff)
{
// limitations...
level = 0xffff;
}
foldLevels[line] = (short)level;
} //}}}
//{{{ setFirstInvalidFoldLevel() method
public void setFirstInvalidFoldLevel(int firstInvalidFoldLevel)
{
this.firstInvalidFoldLevel = firstInvalidFoldLevel;
} //}}}
//{{{ getFirstInvalidFoldLevel() method
public int getFirstInvalidFoldLevel()
{
return firstInvalidFoldLevel;
} //}}}
//{{{ getLineContext() method
public final TokenMarker.LineContext getLineContext(int line)
{
return lineContext[line];
} //}}}
//{{{ setLineContext() method
public final void setLineContext(int line, TokenMarker.LineContext context)
{
lineContext[line] = context;
} //}}}
//{{{ setFirstInvalidLineContext() method
public void setFirstInvalidLineContext(int firstInvalidLineContext)
{
this.firstInvalidLineContext = firstInvalidLineContext;
} //}}}
//{{{ getFirstInvalidLineContext() method
public int getFirstInvalidLineContext()
{
return firstInvalidLineContext;
} //}}}
//{{{ _contentInserted() method
public void _contentInserted(IntegerArray endOffsets)
{
gapLine = -1;
gapWidth = 0;
firstInvalidLineContext = firstInvalidFoldLevel = 0;
lineCount = endOffsets.getSize();
this.endOffsets = endOffsets.getArray();
foldLevels = new short[lineCount];
lineContext = new TokenMarker.LineContext[lineCount];
} //}}}
//{{{ contentInserted() method
public void contentInserted(int startLine, int offset,
int numLines, int length, IntegerArray endOffsets)
{
int endLine = startLine + numLines;
//{{{ Update line info and line context arrays
if(numLines > 0)
{
//moveGap(-1,0,"contentInserted");
lineCount += numLines;
if(this.endOffsets.length <= lineCount)
{
int[] endOffsetsN = new int[(lineCount + 1) * 2];
System.arraycopy(this.endOffsets,0,endOffsetsN,0,
this.endOffsets.length);
this.endOffsets = endOffsetsN;
}
if(foldLevels.length <= lineCount)
{
short[] foldLevelsN = new short[(lineCount + 1) * 2];
System.arraycopy(foldLevels,0,foldLevelsN,0,
foldLevels.length);
foldLevels = foldLevelsN;
}
if(lineContext.length <= lineCount)
{
TokenMarker.LineContext[] lineContextN
= new TokenMarker.LineContext[(lineCount + 1) * 2];
System.arraycopy(lineContext,0,lineContextN,0,
lineContext.length);
lineContext = lineContextN;
}
System.arraycopy(this.endOffsets,startLine,
this.endOffsets,endLine,lineCount - endLine);
System.arraycopy(foldLevels,startLine,foldLevels,
endLine,lineCount - endLine);
System.arraycopy(lineContext,startLine,lineContext,
endLine,lineCount - endLine);
if(startLine <= gapLine)
gapLine += numLines;
else if(gapLine != -1)
offset -= gapWidth;
if(startLine < firstInvalidLineContext)
firstInvalidLineContext += numLines;
for(int i = 0; i < numLines; i++)
{
this.endOffsets[startLine + i] = (offset + endOffsets.get(i));
foldLevels[startLine + i] = 0;
}
} //}}}
if(firstInvalidFoldLevel == -1 || firstInvalidFoldLevel > startLine)
firstInvalidFoldLevel = startLine;
moveGap(endLine,length,"contentInserted");
} //}}}
//{{{ contentRemoved() method
public void contentRemoved(int startLine, int offset,
int numLines, int length)
{
int endLine = startLine + numLines;
//{{{ Update line info and line context arrays
if(numLines > 0)
{
//moveGap(-1,0,"contentRemoved");
if(startLine + numLines < gapLine)
gapLine -= numLines;
else if(startLine < gapLine)
gapLine = startLine;
if(startLine + numLines < firstInvalidLineContext)
firstInvalidLineContext -= numLines;
else if(startLine < firstInvalidLineContext)
firstInvalidLineContext = startLine - 1;
lineCount -= numLines;
System.arraycopy(endOffsets,endLine,endOffsets,
startLine,lineCount - startLine);
System.arraycopy(foldLevels,endLine,foldLevels,
startLine,lineCount - startLine);
System.arraycopy(lineContext,endLine,lineContext,
startLine,lineCount - startLine);
} //}}}
if(firstInvalidFoldLevel == -1 || firstInvalidFoldLevel > startLine)
firstInvalidFoldLevel = startLine;
moveGap(startLine,-length,"contentRemoved");
} //}}}
//{{{ Private members
//{{{ Instance variables
private int[] endOffsets;
private short[] foldLevels;
private TokenMarker.LineContext[] lineContext;
private int lineCount;
/**
* If -1, then there is no gap.
* Otherwise, all lines from this line onwards need to have gapWidth
* added to their end offsets.
*/
private int gapLine;
private int gapWidth;
/**
* If -1, all contexts are valid. Otherwise, all lines after this have
* an invalid context.
*/
private int firstInvalidLineContext;
/**
* If -1, all fold levels are valid. Otherwise, all lines after this
* have an invalid fold level.
*/
private int firstInvalidFoldLevel;
/**
* Performance helper for {@link #getLineOfOffset}.
*/
private int getLineOfOffsetLine = -1;
//}}}
//{{{ setLineEndOffset() method
private void setLineEndOffset(int line, int end)
{
endOffsets[line] = end;
} //}}}
//{{{ moveGap() method
private void moveGap(int newGapLine, int newGapWidth, String method)
{
if(gapLine == -1)
gapWidth = newGapWidth;
else if(newGapLine == -1)
{
if(gapWidth != 0)
{
if(Debug.OFFSET_DEBUG && gapLine != lineCount)
Log.log(Log.DEBUG,this,method + ": update from " + gapLine + " to " + lineCount + " width " + gapWidth);
for(int i = gapLine; i < lineCount; i++)
setLineEndOffset(i,getLineEndOffset(i));
}
gapWidth = newGapWidth;
}
else if(newGapLine < gapLine)
{
if(gapWidth != 0)
{
if(Debug.OFFSET_DEBUG && newGapLine != gapLine)
Log.log(Log.DEBUG,this,method + ": update from " + newGapLine + " to " + gapLine + " width " + gapWidth);
for(int i = newGapLine; i < gapLine; i++)
setLineEndOffset(i,getLineEndOffset(i) - gapWidth);
}
gapWidth += newGapWidth;
}
else //if(newGapLine >= gapLine)
{
if(gapWidth != 0)
{
if(Debug.OFFSET_DEBUG && gapLine != newGapLine)
Log.log(Log.DEBUG,this,method + ": update from " + gapLine + " to " + newGapLine + " width " + gapWidth);
for(int i = gapLine; i < newGapLine; i++)
setLineEndOffset(i,getLineEndOffset(i));
}
gapWidth += newGapWidth;
}
if(newGapLine == lineCount)
gapLine = -1;
else
gapLine = newGapLine;
} //}}}
//}}}
}