001: package gnu.jemacs.swt;
002:
003: import java.util.regex.Matcher;
004: import java.util.regex.Pattern;
005:
006: import gnu.lists.GapVector;
007: import gnu.lists.U32Vector;
008:
009: /**
010: * The purpose of this class is to maintain an ordered set of line offsets for an
011: * SwtCharBuffer.
012: * <p>
013: * With a LineOffsets instance it's possible to map from the number of a line to the text
014: * position where it begins, and back, reasonably fast.
015: * (O(1) for line number to line offset, O(log(#lines)) for line offset to line number)
016: * <p>
017: * LineOffsets extends GapVector with an U32Vector as base, allowing new line offsets to be inserted
018: * quickly during normal text typing.
019: * <p>
020: * <p>
021: * Instances of SwtCharBuffer should hold an instance LineOffsets class and notify it whenever the it's text changes.
022: * The notification happens through the methods:
023: * <p>
024: * <ul>
025: * <li>
026: * textRegionMoved, which should be called when the gap (of SwtCharBuffer) changes
027: * (position or size).
028: * </li>
029: * <li>
030: * textInserted.
031: * </li>
032: * <li>
033: * textDeleted.
034: * </li>
035: * </ul>
036: * <p>
037: * <p>
038: *
039: * TODO: decouple this, using a more general event model..
040: *
041: * Assume that lineOffset is an instance of LineOffsets, held by swtCharBuffer an instance of
042: * SwtCharBuffer.
043: * <p>
044: * Then a value of <code>o</code> at index <code>i</code> in lineOffsets.base
045: * means that the line with line number
046: * <code>n = (i < lOff.gapStart ? i : i + lOff.gapEnd - lOff.gapStart)</code>
047: * <p>
048: * starts at text position
049: * <code>p = (o < swtCB.gapStart ? o : o + swtCB.gapEnd - swtCB.gapStart)</code>
050: * <p>
051: * @author Christian Surlykke
052: * 12-07-2004
053: */
054: public class LineOffsets extends GapVector {
055:
056: public final static int minGapSize = 100;
057: private static Pattern newLinePattern = Pattern
058: .compile("\n|\r\n|\r"); // We recognize all the usual forms of
059: // linedelimiters
060:
061: private U32Vector offsets;
062:
063: public LineOffsets(int initialSize) {
064: super (new U32Vector(Math.max(101, initialSize)));
065: offsets = (U32Vector) base;
066: insertLine(0, 0); // Line 0 allways starts at position 0 -
067: // even if the text is just the empty string
068: }
069:
070: private void setOffset(int index, int Offset) {
071: offsets.setIntAt(index < gapStart ? index : index + gapEnd
072: - gapStart, Offset);
073: }
074:
075: private int getOffset(int index) {
076: return offsets.intAt(index < gapStart ? index : index + gapEnd
077: - gapStart);
078: }
079:
080: public void insertLine(int index, int offSet) {
081: gapReserve(index, 1);
082: offsets.setIntAt(gapStart++, offSet);
083: }
084:
085: public int index2offset(int index) {
086: return offsets.intAt(index < gapStart ? index : index + gapEnd
087: - gapStart);
088: }
089:
090: /**
091: * We seek the line containing a given text offset using a halfing of intervals algorithm. Therefore
092: * the method will use O(log(n)) time, n being the number of lines.
093: *
094: * @see org.eclipse.swt.custom.StyledTextContent#getLineAtOffset(int)
095: */
096: public int offset2index(int offset) {
097: // Adhoc optimization: Very often this class will be asked for the line index belonging to the point
098: // where insertion happens, i.e. at the start of the gap.
099: // We try this before the full search so that we may return in O(1) time in this case.
100: try {
101: if (index2offset(gapStart - 1) <= offset
102: && index2offset(gapStart) > offset) {
103: return gapStart - 1;
104: }
105: } catch (IndexOutOfBoundsException e) {
106: }
107:
108: // The normal search
109: int intervalStart = 0;
110: int intervalEnd = size();
111:
112: // Invariant: offset(intervalStart) <= offset AND offset(intervalEnd) > offset
113: while (intervalEnd > intervalStart + 1) {
114: int middle = (intervalStart + intervalEnd) / 2;
115: if (index2offset(middle) <= offset) {
116: intervalStart = middle;
117: } else {
118: intervalEnd = middle;
119: }
120: }
121:
122: return intervalStart;
123: }
124:
125: public void deleteLines(int firstLine, int numberOfLines) {
126: if (numberOfLines > 0) {
127: int pos = createPos(firstLine, false);
128: removePos(pos, numberOfLines);
129: releasePos(pos);
130: }
131: }
132:
133: public void insertLines(int index, int[] offsets) {
134: if (offsets != null && offsets.length > 0) {
135: // The offsets should comply with:
136: // 0 <= offset[i] < offset[j] for 0 <= i < j
137: //
138:
139: // TOCONSIDER:
140: // Maybe we should define an exception of our own here?
141:
142: if (index2offset(index) > offsets[0]) {
143: throw new IllegalArgumentException();
144: }
145:
146: if (index < size()
147: && offsets[offsets.length - 1] > index2offset(index + 1)) {
148: throw new IllegalArgumentException();
149: }
150:
151: for (int i = 0; i < offsets.length - 1; i++) {
152: if (offsets[i] > offsets[i + 1]) {
153: throw new IllegalArgumentException();
154: }
155: }
156:
157: gapReserve(index + 1, offsets.length);
158: System.arraycopy(offsets, 0, offsets, index + 1,
159: offsets.length);
160: }
161: }
162:
163: public String toString() {
164: StringBuffer sbuf = new StringBuffer();
165: sbuf.append("Lines: {" + size() + ", " + gapStart + ", "
166: + gapEnd);
167: sbuf.append(" [");
168: for (int i = 0; i < size(); i++) {
169: if (i == gapStart) {
170: sbuf.append("|");
171: }
172: sbuf.append(offsets.intAt(i < gapStart ? i : i + gapEnd
173: - gapStart));
174:
175: if (i < size() - 1) {
176: sbuf.append(" ");
177: }
178: }
179: sbuf.append("]}");
180: return sbuf.toString();
181:
182: }
183:
184: public int countLines(String newText) {
185: Matcher m = newLinePattern.matcher(newText);
186: int i = 0;
187: while (m.find()) {
188: i++;
189: }
190: return i;
191: }
192:
193: public int linesInRange(int startOffset, int endOffset) {
194: int indexOfStart = offset2index(startOffset);
195: int indexOfEnd = offset2index(endOffset);
196: return indexOfEnd - indexOfStart;
197: }
198:
199: public void textRegionMoved(int regionStart, int regionEnd,
200: int displacement) {
201: int firstIndexToUpdate = offset2index(regionStart) + 1;
202: int lastIndexToUpdate = offset2index(regionEnd);
203: for (int index = firstIndexToUpdate; index <= lastIndexToUpdate; index++) {
204: setOffset(index, getOffset(index) + displacement);
205: }
206: }
207:
208: public void textInserted(int startOffset, CharSequence seq) {
209: int index = offset2index(startOffset);
210: for (Matcher m = newLinePattern.matcher(seq); m.find();) {
211: insertLine(++index, startOffset + m.end());
212: }
213: }
214:
215: public void textDeleted(int startOffset, int endOffset) {
216: int index = offset2index(startOffset);
217: shiftGap(index + 1);
218: while (gapEnd < offsets.getBufferLength()
219: && offsets.intAt(gapEnd) <= endOffset) {
220: gapEnd++;
221: }
222: }
223:
224: public boolean isLineDelimiter(char c) {
225: // TODO Auto-generated method stub
226: return c == '\n' || c == '\r';
227: }
228: }
|