IDEA-51739
[fedora-idea.git] / platform / platform-impl / src / com / intellij / openapi / editor / impl / LineSet.java
blobe2ccbea22de29781c5897539154ad0d127a87f1c
1 /*
2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.openapi.editor.impl;
18 import com.intellij.openapi.application.ApplicationManager;
19 import com.intellij.openapi.editor.event.DocumentEvent;
20 import com.intellij.openapi.editor.ex.LineIterator;
21 import com.intellij.openapi.editor.ex.util.SegmentArrayWithData;
22 import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
23 import com.intellij.openapi.util.text.LineTokenizer;
24 import com.intellij.util.text.MergingCharSequence;
26 /**
29 public class LineSet{
30 private SegmentArrayWithData mySegments = new SegmentArrayWithData();
31 private static final int MODIFIED_MASK = 0x4;
32 private static final int SEPARATOR_MASK = 0x3;
34 public int findLineIndex(int offset) {
35 return mySegments.findSegmentIndex(offset);
38 public LineIterator createIterator() {
39 return new LineIteratorImpl(this);
42 final int getLineStart(int index) {
43 return mySegments.getSegmentStart(index);
46 final int getLineEnd(int index) {
47 return mySegments.getSegmentEnd(index);
50 final boolean isModified(int index) {
51 return (mySegments.getSegmentData(index) & MODIFIED_MASK) != 0;
54 final int getSeparatorLength(int index) {
55 return mySegments.getSegmentData(index) & SEPARATOR_MASK;
58 final int getLineCount() {
59 return mySegments.getSegmentCount();
62 public void documentCreated(DocumentEvent e) {
63 initSegments(e.getDocument().getCharsSequence(), false);
66 public void changedUpdate(DocumentEvent e1) {
67 DocumentEventImpl e = (DocumentEventImpl) e1;
68 if (e.isOnlyOneLineChanged() && mySegments.getSegmentCount() > 0) {
69 processOneLineChange(e);
70 } else {
71 if (mySegments.getSegmentCount() == 0 || e.getStartOldIndex() >= mySegments.getSegmentCount() ||
72 e.getStartOldIndex() < 0) {
73 initSegments(e.getDocument().getCharsSequence(), true);
74 return;
77 final int optimizedLineShift = e.getOptimizedLineShift();
79 if (optimizedLineShift != -1) {
80 processOptimizedMultilineInsert(e, optimizedLineShift);
81 } else {
82 final int optimizedOldLineShift = e.getOptimizedOldLineShift();
84 if (optimizedOldLineShift != -1) {
85 processOptimizedMultilineDelete(e, optimizedOldLineShift);
86 } else {
87 processMultilineChange(e);
92 if (e.isWholeTextReplaced()) {
93 clearModificationFlags();
97 public static void setTestingMode(boolean testMode) {
98 assert ApplicationManager.getApplication().isUnitTestMode();
99 doTest = testMode;
102 private static boolean doTest = false;
104 private void processOptimizedMultilineDelete(final DocumentEventImpl e, final int optimizedLineShift) {
105 final int insertionPoint = e.getOffset();
106 final int changedLineIndex = e.getStartOldIndex();
107 final int lengthDiff = e.getOldLength();
109 SegmentArrayWithData workingCopySegmentsForTesting = null;
110 SegmentArrayWithData segments; //
112 if (doTest) {
113 segments = new SegmentArrayWithData();
114 workingCopySegmentsForTesting = new SegmentArrayWithData();
115 fillSegments(segments, workingCopySegmentsForTesting);
116 } else {
117 segments = mySegments;
120 final int oldSegmentStart = segments.getSegmentStart(changedLineIndex);
121 final int lastChangedEnd = segments.getSegmentEnd(changedLineIndex + optimizedLineShift);
122 final short lastChangedData = segments.getSegmentData(changedLineIndex + optimizedLineShift);
123 final int newSegmentEnd = oldSegmentStart + (insertionPoint - oldSegmentStart) + (lastChangedEnd - insertionPoint - lengthDiff);
125 segments.remove(changedLineIndex, changedLineIndex + optimizedLineShift);
127 if (newSegmentEnd != 0) {
128 segments.setElementAt(
129 changedLineIndex,
130 oldSegmentStart, newSegmentEnd,
131 lastChangedData | MODIFIED_MASK
133 } else {
134 segments.remove(changedLineIndex, changedLineIndex + 1);
137 // update data after lineIndex, shifting with optimizedLineShift
138 final int segmentCount = segments.getSegmentCount();
139 for(int i = changedLineIndex + 1; i < segmentCount; ++i) {
140 segments.setElementAt(i, segments.getSegmentStart(i) - lengthDiff,
141 segments.getSegmentEnd(i) - lengthDiff,
142 segments.getSegmentData(i)
146 if (doTest) {
147 final SegmentArrayWithData data = mySegments;
148 mySegments = segments;
149 addEmptyLineAtEnd();
151 doCheckResults(workingCopySegmentsForTesting, e, data, segments);
152 } else {
153 addEmptyLineAtEnd();
157 private void processOptimizedMultilineInsert(final DocumentEventImpl e, final int optimizedLineShift) {
158 final int insertionPoint = e.getOffset();
159 final int changedLineIndex = e.getStartOldIndex();
160 final int lengthDiff = e.getNewLength();
161 final LineTokenizer tokenizer = new LineTokenizer(e.getNewFragment());
163 SegmentArrayWithData workingCopySegmentsForTesting = null;
164 SegmentArrayWithData segments; //
166 if (doTest) {
167 segments = new SegmentArrayWithData();
168 workingCopySegmentsForTesting = new SegmentArrayWithData();
169 fillSegments(segments, workingCopySegmentsForTesting);
170 } else {
171 segments = mySegments;
174 int i;
176 // update data after lineIndex, shifting with optimizedLineShift
177 for(i = segments.getSegmentCount() - 1; i > changedLineIndex; --i) {
178 segments.setElementAt(i + optimizedLineShift, segments.getSegmentStart(i) + lengthDiff,
179 segments.getSegmentEnd(i) + lengthDiff,
180 segments.getSegmentData(i)
184 final int oldSegmentEnd = segments.getSegmentEnd(changedLineIndex);
185 final int oldSegmentStart = segments.getSegmentStart(changedLineIndex);
186 final short oldSegmentData = segments.getSegmentData(changedLineIndex);
188 final int newChangedLineEnd = insertionPoint + tokenizer.getLineSeparatorLength() + tokenizer.getOffset() + tokenizer.getLength();
189 segments.setElementAt(
190 changedLineIndex,
191 oldSegmentStart, newChangedLineEnd,
192 tokenizer.getLineSeparatorLength() | MODIFIED_MASK
195 tokenizer.advance();
196 i = 1;
197 int lastFragmentLength = 0;
199 while(!tokenizer.atEnd()) {
200 lastFragmentLength = tokenizer.getLineSeparatorLength() != 0 ? 0:tokenizer.getLength();
201 segments.setElementAt(
202 changedLineIndex + i,
203 insertionPoint + tokenizer.getOffset(),
204 insertionPoint + tokenizer.getOffset() + tokenizer.getLength() + tokenizer.getLineSeparatorLength(),
205 tokenizer.getLineSeparatorLength() | MODIFIED_MASK
207 i++;
208 tokenizer.advance();
211 segments.setElementAt(
212 changedLineIndex + optimizedLineShift, insertionPoint + lengthDiff - lastFragmentLength,
213 oldSegmentEnd + lengthDiff,
214 oldSegmentData | MODIFIED_MASK
217 if (doTest) {
218 final SegmentArrayWithData data = mySegments;
219 mySegments = segments;
220 addEmptyLineAtEnd();
222 doCheckResults(workingCopySegmentsForTesting, e, data, segments);
223 } else {
224 addEmptyLineAtEnd();
228 private void doCheckResults(final SegmentArrayWithData workingCopySegmentsForTesting, final DocumentEventImpl e,
229 final SegmentArrayWithData data,
230 final SegmentArrayWithData segments) {
231 mySegments = workingCopySegmentsForTesting;
232 processMultilineChange(e);
233 mySegments = data;
235 assert workingCopySegmentsForTesting.getSegmentCount() == segments.getSegmentCount();
236 for(int i =0; i < segments.getSegmentCount();++i) {
237 assert workingCopySegmentsForTesting.getSegmentStart(i) == segments.getSegmentStart(i);
238 assert workingCopySegmentsForTesting.getSegmentEnd(i) == segments.getSegmentEnd(i);
239 assert workingCopySegmentsForTesting.getSegmentData(i) == segments.getSegmentData(i);
242 processMultilineChange(e);
245 private void fillSegments(final SegmentArrayWithData segments, final SegmentArrayWithData workingCopySegmentsForTesting) {
246 for(int i = mySegments.getSegmentCount() - 1; i >=0; --i) {
247 segments.setElementAt(
249 mySegments.getSegmentStart(i),
250 mySegments.getSegmentEnd(i),
251 mySegments.getSegmentData(i)
253 workingCopySegmentsForTesting.setElementAt(
255 mySegments.getSegmentStart(i),
256 mySegments.getSegmentEnd(i),
257 mySegments.getSegmentData(i)
262 private void processMultilineChange(DocumentEventImpl e) {
263 int offset = e.getOffset();
264 CharSequence newString = e.getNewFragment();
265 CharSequence chars = e.getDocument().getCharsSequence();
267 int oldStartLine = e.getStartOldIndex();
268 int offset1 = getLineStart(oldStartLine);
269 if (offset1 != offset) {
270 CharSequence prefix = chars.subSequence(offset1, offset);
271 newString = new MergingCharSequence(prefix, newString);
274 int oldEndLine = findLineIndex(e.getOffset() + e.getOldLength());
275 if (oldEndLine < 0) {
276 oldEndLine = getLineCount() - 1;
278 int offset2 = getLineEnd(oldEndLine);
279 if (offset2 != offset + e.getOldLength()) {
280 final int start = offset + e.getNewLength();
281 final int length = offset2 - offset - e.getOldLength();
282 CharSequence postfix = chars.subSequence(start, start + length);
283 newString = new MergingCharSequence(newString, postfix);
286 updateSegments(newString, oldStartLine, oldEndLine, offset1, e);
287 // We add empty line at the end, if the last line ends by line separator.
288 addEmptyLineAtEnd();
291 private void updateSegments(CharSequence newText, int oldStartLine, int oldEndLine, int offset1,
292 DocumentEventImpl e) {
293 int count = 0;
294 LineTokenizer lineTokenizer = new LineTokenizer(newText);
295 for (int index = oldStartLine; index <= oldEndLine; index++) {
296 if (!lineTokenizer.atEnd()) {
297 setSegmentAt(mySegments, index, lineTokenizer, offset1, true);
298 lineTokenizer.advance();
299 } else {
300 mySegments.remove(index, oldEndLine + 1);
301 break;
303 count++;
305 if (!lineTokenizer.atEnd()) {
306 SegmentArrayWithData insertSegments = new SegmentArrayWithData();
307 int i = 0;
308 while (!lineTokenizer.atEnd()) {
309 setSegmentAt(insertSegments, i, lineTokenizer, offset1, true);
310 lineTokenizer.advance();
311 count++;
312 i++;
314 mySegments.insert(insertSegments, oldEndLine + 1);
316 int shift = e.getNewLength() - e.getOldLength();
317 mySegments.shiftSegments(oldStartLine + count, shift);
320 private void processOneLineChange(DocumentEventImpl e) {
321 // Check, if the change on the end of text
322 if (e.getOffset() >= mySegments.getSegmentEnd(mySegments.getSegmentCount() - 1)) {
323 mySegments.changeSegmentLength(mySegments.getSegmentCount() - 1, e.getNewLength() - e.getOldLength());
324 setSegmentModified(mySegments, mySegments.getSegmentCount() - 1);
325 } else {
326 mySegments.changeSegmentLength(e.getStartOldIndex(), e.getNewLength() - e.getOldLength());
327 setSegmentModified(mySegments, e.getStartOldIndex());
331 public void clearModificationFlags() {
332 for (int i = 0; i < mySegments.getSegmentCount(); i++) {
333 mySegments.setSegmentData(i, mySegments.getSegmentData(i) & ~MODIFIED_MASK);
337 private static void setSegmentAt(SegmentArrayWithData segmentArrayWithData, int index, LineTokenizer lineTokenizer, int offsetShift, boolean isModified) {
338 int offset = lineTokenizer.getOffset() + offsetShift;
339 int length = lineTokenizer.getLength();
340 int separatorLength = lineTokenizer.getLineSeparatorLength();
341 int separatorAndModifiedFlag = separatorLength;
342 if(isModified) {
343 separatorAndModifiedFlag |= MODIFIED_MASK;
345 segmentArrayWithData.setElementAt(index, offset, offset + length + separatorLength, separatorAndModifiedFlag);
348 private static void setSegmentModified(SegmentArrayWithData segments, int i) {
349 segments.setSegmentData(i, segments.getSegmentData(i)|MODIFIED_MASK);
352 private void initSegments(CharSequence text, boolean toSetModified) {
353 mySegments.removeAll();
354 LineTokenizer lineTokenizer = new LineTokenizer(text);
355 int i = 0;
356 while(!lineTokenizer.atEnd()) {
357 setSegmentAt(mySegments, i, lineTokenizer, 0, toSetModified);
358 i++;
359 lineTokenizer.advance();
361 // We add empty line at the end, if the last line ends by line separator.
362 addEmptyLineAtEnd();
365 // Add empty line at the end, if the last line ends by line separator.
366 private void addEmptyLineAtEnd() {
367 int segmentCount = mySegments.getSegmentCount();
368 if(segmentCount > 0 && getSeparatorLength(segmentCount-1) > 0) {
369 mySegments.setElementAt(segmentCount, mySegments.getSegmentEnd(segmentCount-1), mySegments.getSegmentEnd(segmentCount-1), 0);
370 setSegmentModified(mySegments, segmentCount);