package org.eclipse.jface.text; | |
/* | |
* (c) Copyright IBM Corp. 2000, 2001. | |
* All Rights Reserved. | |
*/ | |
import java.util.ArrayList; | |
import java.util.List; | |
/** | |
* Abstract implementation of <code>ILineTracker</code>. It lets the | |
* definition of line delimiters to subclasses. Assuming that '\n' is | |
* the only line delimiter, this abstract implementation defines the | |
* following line scheme: | |
* <ul> | |
* <li> "" -> [0,0] | |
* <li> "a" -> [0,1] | |
* <li> "\n" -> [0,1], [1,0] | |
* <li> "a\n" -> [0,2], [2,0] | |
* <li> "a\nb" -> [0,2], [2,1] | |
* <li> "a\nbc\n" -> [0,2], [2,3], [5,0] | |
* </ul> | |
* This class must be subclassed. | |
*/ | |
public abstract class AbstractLineTracker implements ILineTracker { | |
/** | |
* Combines the information of the occurence of a line delimiter. | |
* <code>delimiterIndex</code> is the index where a line delimiter | |
* starts, whereas <code>delimiterLength</code>, indicates the length | |
* of the delimiter. | |
*/ | |
protected static class DelimiterInfo { | |
public int delimiterIndex; | |
public int delimiterLength; | |
public String delimiter; | |
}; | |
/** The line information */ | |
private List fLines= new ArrayList(); | |
/** The length of the tracked text */ | |
private int fTextLength; | |
/** | |
* Creates a new line tracker. | |
*/ | |
protected AbstractLineTracker() { | |
} | |
/** | |
* Binary search for the line at a given offset. | |
* | |
* @param offset the offset whose line should be found | |
* @return the line of the offset | |
*/ | |
private int findLine(int offset) { | |
if (fLines.size() == 0) | |
return -1; | |
int left= 0; | |
int right= fLines.size() -1; | |
int mid= 0; | |
Line line= null; | |
while (left < right) { | |
mid= (left + right) / 2; | |
line= (Line) fLines.get(mid); | |
if (offset < line.offset) { | |
if (left == mid) | |
right= left; | |
else | |
right= mid -1; | |
} else if (offset > line.offset) { | |
if (right == mid) | |
left= right; | |
else | |
left= mid +1; | |
} else if (offset == line.offset) { | |
left= right= mid; | |
} | |
} | |
line= (Line) fLines.get(left); | |
if (line.offset > offset) | |
-- left; | |
return left; | |
} | |
/** | |
* Returns the number of lines covered by the specified text range. | |
* | |
* @param startLine the line where the text range starts | |
* @param offset the start offset of the text range | |
* @param length the length of the text range | |
* @return the number of lines covered by this text range | |
* @exception BadLocationException if range is undefined in this tracker | |
*/ | |
private int getNumberOfLines(int startLine, int offset, int length) throws BadLocationException { | |
if (length == 0) | |
return 1; | |
int target= offset + length; | |
Line l= (Line) fLines.get(startLine); | |
if (l.delimiter == null) | |
return 1; | |
if (l.offset + l.length > target) | |
return 1; | |
if (l.offset + l.length == target) | |
return 2; | |
return getLineNumberOfOffset(target) - startLine + 1; | |
} | |
/* | |
* @see ILineTracker#getLineLength | |
*/ | |
public int getLineLength(int line) throws BadLocationException { | |
int lines= fLines.size(); | |
if (line < 0 || line > lines) | |
throw new BadLocationException(); | |
if (lines == 0 || lines == line) | |
return 0; | |
Line l= (Line) fLines.get(line); | |
return l.length; | |
} | |
/* | |
* @see ILineTracker#getLineNumberOfOffset | |
*/ | |
public int getLineNumberOfOffset(int position) throws BadLocationException { | |
if (position > fTextLength) | |
throw new BadLocationException(); | |
if (position == fTextLength) { | |
int lastLine= fLines.size() - 1; | |
if (lastLine < 0) | |
return 0; | |
Line l= (Line) fLines.get(lastLine); | |
return (l.delimiter != null ? lastLine + 1 : lastLine); | |
} | |
return findLine(position); | |
} | |
/* | |
* @see ILineTracker#getLineInformationOfOffset | |
*/ | |
public IRegion getLineInformationOfOffset(int position) throws BadLocationException { | |
if (position > fTextLength) | |
throw new BadLocationException(); | |
if (position == fTextLength) { | |
int size= fLines.size(); | |
if (size == 0) | |
return new Region(0, 0); | |
Line l= (Line) fLines.get(size - 1); | |
return (l.delimiter != null ? new Line(fTextLength, 0) : new Line(fTextLength - l.length, l.length)); | |
} | |
return getLineInformation(findLine(position)); | |
} | |
/* | |
* @see ILineTracker#getLineInformation | |
*/ | |
public IRegion getLineInformation(int line) throws BadLocationException { | |
int lines= fLines.size(); | |
if (line < 0 || line > lines) | |
throw new BadLocationException(); | |
if (lines == 0) | |
return new Line(0, 0); | |
if (line == lines) { | |
Line l= (Line) fLines.get(line - 1); | |
return new Line(l.offset + l.length, 0); | |
} | |
Line l= (Line) fLines.get(line); | |
return (l.delimiter != null ? new Line(l.offset, l.length - l.delimiter.length()) : l); | |
} | |
/* | |
* @see ILineTracker#getLineOffset | |
*/ | |
public int getLineOffset(int line) throws BadLocationException { | |
int lines= fLines.size(); | |
if (line < 0 || line > lines) | |
throw new BadLocationException(); | |
if (lines == 0) | |
return 0; | |
if (line == lines) { | |
Line l= (Line) fLines.get(line - 1); | |
return l.offset + l.length; | |
} | |
Line l= (Line) fLines.get(line); | |
return l.offset; | |
} | |
/* | |
* @see ILineTracker#getNumberOfLines | |
*/ | |
public int getNumberOfLines() { | |
int lines= fLines.size(); | |
if (lines == 0) | |
return 1; | |
Line l= (Line) fLines.get(lines - 1); | |
return (l.delimiter != null ? lines + 1 : lines); | |
} | |
/* | |
* @see ILineTracker#getNumberOfLines(int, int) | |
*/ | |
public int getNumberOfLines(int position, int length) throws BadLocationException { | |
if (position < 0 || position + length > fTextLength) | |
throw new BadLocationException(); | |
if (length == 0) // optimization | |
return 1; | |
return getNumberOfLines(getLineNumberOfOffset(position), position, length); | |
} | |
/* | |
* @see ILineTracker#computeNumberOfLines(String) | |
*/ | |
public int computeNumberOfLines(String text) { | |
int count= 0; | |
int start= 0; | |
DelimiterInfo delimiterInfo= nextDelimiterInfo(text, start); | |
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) { | |
++count; | |
start= delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength; | |
delimiterInfo= nextDelimiterInfo(text, start); | |
} | |
return count; | |
} | |
/* ----------------- manipulation ------------------------------ */ | |
/** | |
* Returns the info of the first delimiter found in the given | |
* text starting at the given offset. | |
* | |
* @param text the text to be searched | |
* @param offset the offset in the given text | |
* @return the info of the first found delimiter or <code>null</code> if | |
* there is no such info | |
*/ | |
protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset); | |
/** | |
* Creates the line structure for the given text. Newly created lines | |
* are inserted into the line structure starting at the given | |
* position. Returns the number of newly created lines. | |
* | |
* @param text the text for which to create a line structure | |
* @param insertPosition the position at which the newly created lines are inserted | |
* into the tracker's line structure | |
* @param offset the offset of all newly created lines | |
* @return the number of newly created lines | |
*/ | |
private int createLines(String text, int insertPosition, int offset) { | |
int count= 0; | |
int start= 0; | |
DelimiterInfo delimiterInfo= nextDelimiterInfo(text, 0); | |
while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) { | |
int index= delimiterInfo.delimiterIndex + (delimiterInfo.delimiterLength - 1); | |
if (insertPosition + count >= fLines.size()) | |
fLines.add(new Line(offset + start, offset + index, delimiterInfo.delimiter)); | |
else | |
fLines.add(insertPosition + count, new Line(offset + start, offset + index, delimiterInfo.delimiter)); | |
++count; | |
start= index + 1; | |
delimiterInfo= nextDelimiterInfo(text, start); | |
} | |
if (start < text.length()) { | |
if (insertPosition + count < fLines.size()) { | |
// there is a line below the current | |
Line l= (Line) fLines.get(insertPosition + count); | |
int delta= text.length() - start; | |
l.offset -= delta; | |
l.length += delta; | |
} else { | |
fLines.add(new Line(offset + start, offset + text.length() - 1, null)); | |
++count; | |
} | |
} | |
return count; | |
} | |
/** | |
* Keeps track of the line information when text is inserted. | |
* Returns the number of inserted lines. | |
* | |
* @param lineNumber the line at which the insert happens | |
* @param offset at which the insert happens | |
* @param text the inserted text | |
* @return the number of inserted lines | |
* @exception BadLocationException if offset is invalid in this tracker | |
*/ | |
private int insert(int lineNumber, int offset, String text) throws BadLocationException { | |
if (text == null || text.length() == 0) | |
return 0; | |
fTextLength += text.length(); | |
int size= fLines.size(); | |
if (size == 0 || lineNumber >= size) | |
return createLines(text, size, offset); | |
Line line= (Line) fLines.get(lineNumber); | |
DelimiterInfo delimiterInfo= nextDelimiterInfo(text, 0); | |
if (delimiterInfo == null || delimiterInfo.delimiterIndex == -1) { | |
line.length += text.length(); | |
return 0; | |
} | |
// as there is a line break, split line but do so only if rest of line is not of length 0 | |
int restLength= line.offset + line.length - offset; | |
if (restLength > 0) { | |
// determine start and end of the second half of the splitted line | |
Line lineRest= new Line(offset, restLength); | |
lineRest.delimiter= line.delimiter; | |
// shift it by the inserted text | |
lineRest.offset += text.length(); | |
// and insert in line structure | |
fLines.add(lineNumber + 1, lineRest); | |
} | |
// adapt the beginning of the splitted line | |
line.delimiter= delimiterInfo.delimiter; | |
int nextStart= offset + delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength; | |
line.length= nextStart - line.offset; | |
// insert lines for the remaining text | |
text= text.substring(delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength); | |
return createLines(text, lineNumber + 1, nextStart) + 1; | |
} | |
/** | |
* Keeps track of the line information when text is removed. Returns | |
* whether the line at which the deletion start will thereby be deleted. | |
* | |
* @param lineNumber the lineNumber at which the deletion starts | |
* @param offset the offset of the first deleted character | |
* @param length the number of deleted characters | |
* @return whethere the start line of the deletion has been deleted | |
* @exception BadLocationException if position is unkown to the tracker | |
*/ | |
private boolean remove(int lineNumber, int offset, int length) throws BadLocationException { | |
if (length == 0) | |
return false; | |
int removedLineEnds= getNumberOfLines(lineNumber, offset, length) - 1; | |
Line line= (Line) fLines.get(lineNumber); | |
if ((lineNumber == fLines.size() - 1) && removedLineEnds > 0) { | |
line.length -= length; | |
line.delimiter= null; | |
} else { | |
++ lineNumber; | |
for (int i= 1; i <= removedLineEnds; i++) { | |
if (lineNumber == fLines.size()) { | |
line.delimiter= null; | |
break; | |
} | |
Line line2= (Line) fLines.get(lineNumber); | |
line.length += line2.length; | |
line.delimiter= line2.delimiter; | |
fLines.remove(lineNumber); | |
} | |
line.length -= length; | |
} | |
fTextLength -= length; | |
if (line.length == 0) { | |
fLines.remove(line); | |
return true; | |
} | |
return false; | |
} | |
/** | |
* Adapts the offset of all lines with line numbers greater than the specified | |
* one to the given delta. | |
* | |
* @param lineNumber the line number after which to start | |
* @param delta the offset delta to be applied | |
*/ | |
private void adaptLineOffsets(int lineNumber, int delta) { | |
int size= fLines.size(); | |
for (int i= lineNumber + 1; i < size; i++) { | |
Line l= (Line) fLines.get(i); | |
l.offset += delta; | |
} | |
} | |
/* | |
* @see ILineTracker#replace | |
*/ | |
public void replace(int position, int length, String text) throws BadLocationException { | |
int lineNumber= getLineNumberOfOffset(position); | |
int insertLineNumber= lineNumber; | |
if (remove(lineNumber, position, length)) | |
-- lineNumber; | |
lineNumber += insert(insertLineNumber, position, text); | |
int delta= -length; | |
if (text != null) | |
delta= text.length() + delta; | |
if (delta != 0) | |
adaptLineOffsets(lineNumber, delta); | |
} | |
/* | |
* @see ILineTracker#set | |
*/ | |
public void set(String text) { | |
fLines.clear(); | |
if (text != null) { | |
fTextLength= text.length(); | |
createLines(text, 0, 0); | |
} | |
} | |
/* | |
* @see ILineTracker#getLineDelimiter | |
*/ | |
public String getLineDelimiter(int line) throws BadLocationException { | |
int lines= fLines.size(); | |
if (line < 0 || line > lines) | |
throw new BadLocationException(); | |
if (lines == 0) | |
return null; | |
if (line == lines) | |
return null; | |
Line l= (Line) fLines.get(line); | |
return l.delimiter; | |
} | |
} |