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
;
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
);
71 if (mySegments
.getSegmentCount() == 0 || e
.getStartOldIndex() >= mySegments
.getSegmentCount() ||
72 e
.getStartOldIndex() < 0) {
73 initSegments(e
.getDocument().getCharsSequence(), true);
77 final int optimizedLineShift
= e
.getOptimizedLineShift();
79 if (optimizedLineShift
!= -1) {
80 processOptimizedMultilineInsert(e
, optimizedLineShift
);
82 final int optimizedOldLineShift
= e
.getOptimizedOldLineShift();
84 if (optimizedOldLineShift
!= -1) {
85 processOptimizedMultilineDelete(e
, optimizedOldLineShift
);
87 processMultilineChange(e
);
92 if (e
.isWholeTextReplaced()) {
93 clearModificationFlags();
97 public static void setTestingMode(boolean testMode
) {
98 assert ApplicationManager
.getApplication().isUnitTestMode();
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
; //
113 segments
= new SegmentArrayWithData();
114 workingCopySegmentsForTesting
= new SegmentArrayWithData();
115 fillSegments(segments
, workingCopySegmentsForTesting
);
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(
130 oldSegmentStart
, newSegmentEnd
,
131 lastChangedData
| MODIFIED_MASK
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
)
147 final SegmentArrayWithData data
= mySegments
;
148 mySegments
= segments
;
151 doCheckResults(workingCopySegmentsForTesting
, e
, data
, segments
);
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
; //
167 segments
= new SegmentArrayWithData();
168 workingCopySegmentsForTesting
= new SegmentArrayWithData();
169 fillSegments(segments
, workingCopySegmentsForTesting
);
171 segments
= mySegments
;
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(
191 oldSegmentStart
, newChangedLineEnd
,
192 tokenizer
.getLineSeparatorLength() | MODIFIED_MASK
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
211 segments
.setElementAt(
212 changedLineIndex
+ optimizedLineShift
, insertionPoint
+ lengthDiff
- lastFragmentLength
,
213 oldSegmentEnd
+ lengthDiff
,
214 oldSegmentData
| MODIFIED_MASK
218 final SegmentArrayWithData data
= mySegments
;
219 mySegments
= segments
;
222 doCheckResults(workingCopySegmentsForTesting
, e
, data
, segments
);
228 private void doCheckResults(final SegmentArrayWithData workingCopySegmentsForTesting
, final DocumentEventImpl e
,
229 final SegmentArrayWithData data
,
230 final SegmentArrayWithData segments
) {
231 mySegments
= workingCopySegmentsForTesting
;
232 processMultilineChange(e
);
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.
291 private void updateSegments(CharSequence newText
, int oldStartLine
, int oldEndLine
, int offset1
,
292 DocumentEventImpl e
) {
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();
300 mySegments
.remove(index
, oldEndLine
+ 1);
305 if (!lineTokenizer
.atEnd()) {
306 SegmentArrayWithData insertSegments
= new SegmentArrayWithData();
308 while (!lineTokenizer
.atEnd()) {
309 setSegmentAt(insertSegments
, i
, lineTokenizer
, offset1
, true);
310 lineTokenizer
.advance();
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);
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
;
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
);
356 while(!lineTokenizer
.atEnd()) {
357 setSegmentAt(mySegments
, i
, lineTokenizer
, 0, toSetModified
);
359 lineTokenizer
.advance();
361 // We add empty line at the end, if the last line ends by line separator.
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
);