mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
remove unused DocumentChangesCollector
This commit is contained in:
-305
@@ -1,305 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2011 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.application.options;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.event.DocumentListener;
|
||||
import com.intellij.openapi.editor.impl.TextChangeImpl;
|
||||
import gnu.trove.TIntArrayList;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Aggregates, merges and exposes information about {@link Document document} changes.
|
||||
* <p/>
|
||||
* Intended usage scenario is to collect information about format preview document changes occurred during
|
||||
* tweaking format options and highlight corresponding sections.
|
||||
* <p/>
|
||||
* <b>Note:</b> current algorithm does consider changing particular text by the same text to be a document change, i.e. consider
|
||||
* the document like <code>'abcde'</code>. Suppose it's text from range <code>[2; 4)</code> (<code>'cd'</code>) is replaced by the same
|
||||
* text. It's still to be considered as a change.
|
||||
* <p/>
|
||||
* Not thread-safe.
|
||||
*
|
||||
* @author Denis Zhdanov
|
||||
* @since 10/11/10 5:10 PM
|
||||
*/
|
||||
public class DocumentChangesCollector implements DocumentListener {
|
||||
|
||||
/**
|
||||
* Holds merged document changes.
|
||||
* <p/>
|
||||
* Is assumed to be always sorted by change start offset in ascending order.
|
||||
*/
|
||||
private final List<TextChangeImpl> myChanges = new ArrayList<TextChangeImpl>();
|
||||
|
||||
private boolean myCollectChanges;
|
||||
|
||||
@Override
|
||||
public void beforeDocumentChange(DocumentEvent event) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void documentChanged(DocumentEvent event) {
|
||||
if (!myCollectChanges) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The general algorithm is as follows:
|
||||
// 1. Drop or cut stored change ranges for removed range if any;
|
||||
// 2. Update offsets of all ranges that lays after the last change range identified by the given document change event;
|
||||
// 3. Merge added range with registered changes if any;
|
||||
// 4. Merge all adjacent ranges if any;
|
||||
StringBuilder oldText = new StringBuilder(event.getOldFragment());
|
||||
|
||||
cutChangesIfNecessary(event, oldText);
|
||||
updateChanges(event, oldText);
|
||||
mergeChangesIfNecessary(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cuts or removes stored change ranges for the given interval if any.
|
||||
*
|
||||
* @param event event for just occurred document change
|
||||
* @param oldText our main idea is to merge all changes in order to have aggregated change against original document. So, there is
|
||||
* a possible case that subsequent change affects text inserted by the previous one. We don't want to reflect that
|
||||
* at aggregated change as it didn't appear in initial document. Hence, we have a mutable symbols buffer that holds
|
||||
* symbols from initial document affected by the given change
|
||||
*/
|
||||
private void cutChangesIfNecessary(DocumentEvent event, StringBuilder oldText) {
|
||||
if (myChanges.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int start = event.getOffset();
|
||||
int end = event.getOffset() + event.getOldLength();
|
||||
int diff = event.getNewLength() - event.getOldLength();
|
||||
|
||||
int forwardIndex = findIndex(start);
|
||||
int backwardIndexStart = forwardIndex - 1;
|
||||
if (forwardIndex < 0) {
|
||||
backwardIndexStart = myChanges.size() - 1;
|
||||
}
|
||||
else {
|
||||
TIntArrayList indices = new TIntArrayList();
|
||||
for (; forwardIndex < myChanges.size(); forwardIndex++) {
|
||||
TextChangeImpl change = myChanges.get(forwardIndex);
|
||||
// Stored change is not affected by the current change.
|
||||
if (change.getStart() >= end) {
|
||||
change.advance(diff);
|
||||
}
|
||||
|
||||
// Stored change region is completely covered by the given change.
|
||||
else if (change.getEnd() <= end) {
|
||||
indices.add(forwardIndex);
|
||||
|
||||
int deleteStart = start - change.getStart();
|
||||
deleteStart = Math.max(0, deleteStart);
|
||||
|
||||
int deleteEnd = change.getEnd() - change.getStart();
|
||||
deleteEnd = Math.min(oldText.length(), Math.max(0, deleteEnd));
|
||||
|
||||
oldText.delete(deleteStart, deleteEnd);
|
||||
oldText.insert(0, change.getText());
|
||||
}
|
||||
|
||||
// Stored change region's start is covered by the current change.
|
||||
else {
|
||||
int deleteStart = change.getStart() - start;
|
||||
deleteStart = Math.min(oldText.length(), Math.max(0, deleteStart));
|
||||
|
||||
int deleteEnd = oldText.length();
|
||||
deleteEnd = Math.min(oldText.length(), Math.max(0, deleteEnd));
|
||||
|
||||
oldText.delete(deleteStart, deleteEnd);
|
||||
myChanges.set(forwardIndex, new TextChangeImpl(change.getText(), end + diff, change.getEnd() + diff));
|
||||
}
|
||||
}
|
||||
|
||||
if (!indices.isEmpty()) {
|
||||
for (int i = indices.size() - 1; i >= 0; i--) {
|
||||
myChanges.remove(indices.get(i));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = Math.min(backwardIndexStart, myChanges.size() - 1); i >= 0; i--) {
|
||||
TextChangeImpl change = myChanges.get(i);
|
||||
if (change.getEnd() <= start) {
|
||||
break;
|
||||
}
|
||||
|
||||
CharSequence textToUse = change.getText();
|
||||
int symbolsToCut = Math.min(change.getEnd(), end) - start;
|
||||
if (textToUse.length() >= symbolsToCut) {
|
||||
oldText.insert(symbolsToCut, textToUse.subSequence(textToUse.length() - symbolsToCut, textToUse.length()));
|
||||
textToUse = textToUse.subSequence(0, textToUse.length() - symbolsToCut);
|
||||
}
|
||||
oldText.delete(0, symbolsToCut);
|
||||
myChanges.set(i, new TextChangeImpl(textToUse, change.getStart(), start));
|
||||
if (change.getEnd() > end) {
|
||||
int shift = event.getOffset() + event.getNewLength() - end;
|
||||
TextChangeImpl changeTail = new TextChangeImpl("", end + shift, change.getEnd() + shift);
|
||||
if (i >= myChanges.size() - 1) {
|
||||
myChanges.add(changeTail);
|
||||
}
|
||||
else {
|
||||
myChanges.add(i + 1, changeTail);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates current object's state on the basis of the given event assuming that there are no stored change ranges that
|
||||
* start after offset denoted by the given event.
|
||||
*
|
||||
* @param event event for just occurred document change
|
||||
* @param oldText our main idea is to merge all changes in order to have aggregated change against original document. So, there is
|
||||
* a possible case that subsequent change affects text inserted by the previous one. We don't want to reflect that
|
||||
* at aggregated change as it didn't appear in initial document. Hence, we have a mutable symbols buffer that holds
|
||||
* symbols from initial document affected by the given change
|
||||
*/
|
||||
private void updateChanges(DocumentEvent event, StringBuilder oldText) {
|
||||
int i = findIndex(event.getOffset());
|
||||
int endOffset = event.getOffset() + event.getNewLength();
|
||||
TextChangeImpl change = new TextChangeImpl(oldText, event.getOffset(), endOffset);
|
||||
if (i < 0) {
|
||||
myChanges.add(change);
|
||||
}
|
||||
else {
|
||||
myChanges.add(i, change);
|
||||
}
|
||||
}
|
||||
|
||||
private void mergeChangesIfNecessary(DocumentEvent event) {
|
||||
// There is a possible case that we had more than one scattered change (e.g. (3; 5) and (8; 10)) and current document change affects
|
||||
// both of them (e.g. remove all symbols from offset (4; 9)). We have two changes then: (3; 4) and (4; 5) and want to merge them
|
||||
// into a single one.
|
||||
if (myChanges.size() < 2) {
|
||||
return;
|
||||
}
|
||||
TextChangeImpl next = myChanges.get(myChanges.size() - 1);
|
||||
for (int i = myChanges.size() - 2; i >= 0; i--) {
|
||||
TextChangeImpl current = myChanges.get(i);
|
||||
if (current.getEnd() < event.getOffset()) {
|
||||
// Assuming that change ranges are always kept at normalized form.
|
||||
break;
|
||||
}
|
||||
if (current.getEnd() == next.getStart()) {
|
||||
myChanges.set(i, next = new TextChangeImpl(current.getText().toString() + next.getText(), current.getStart(), next.getEnd()));
|
||||
myChanges.remove(i + 1);
|
||||
}
|
||||
else {
|
||||
next = current;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns aggregated and merged document changes sorted by their start offsets in ascending order.
|
||||
* <p/>
|
||||
* <b>Example</b>
|
||||
* <pre>
|
||||
* <ol>
|
||||
* <li>Suppose we have a document with text '0123456';</li>
|
||||
* <li>
|
||||
* Document range <code>[1; 3)</code> is removed (text <code>'12'</code> is removed, current text is
|
||||
* <code>'03456'</code>);
|
||||
* </li>
|
||||
* <li>
|
||||
* Document range <code>[1; 4)</code> (<code>'345'</code>) is replaced to <code>'abc'</code>
|
||||
* (current text is <code>'0abc6'</code>);
|
||||
* </li>
|
||||
* </ol>
|
||||
* </pre>
|
||||
* <p/>
|
||||
* So, initial document is <code>'0123456'</code>; final one is <code>'0abc6'</code>. We expect single aggregated change
|
||||
* to be returned then - <code>[1; 4)</code> with text <code>'12345'</code>, i.e. the range defines offset within the
|
||||
* current document and text shows initial document text at that range.
|
||||
*
|
||||
* @return aggregated and merged document changes sorted by their start offsets in ascending order
|
||||
*/
|
||||
@NotNull
|
||||
public List<? extends TextChange> getChanges() {
|
||||
return myChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows to switch <code>'collect document changes'</code> mode.
|
||||
* <p/>
|
||||
* Default value is <code>'false'</code>, i.e. document changes are not collected.
|
||||
*
|
||||
* @param collectChanges new value of <code>'collect document changes'</code> mode
|
||||
*/
|
||||
public void setCollectChanges(boolean collectChanges) {
|
||||
myCollectChanges = collectChanges;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces current object to drop all information about document changes if any.
|
||||
*/
|
||||
public void reset() {
|
||||
myChanges.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds index of the {@link #myCollectChanges first stored changed document ranges} which start offset is equal or greater to the
|
||||
* given one.
|
||||
*
|
||||
* @param offset start offset of target document change
|
||||
* @return non-negative value as an indication that there is not stored changed document range which start offset is equal
|
||||
* or greater than the given offset; negative value otherwise
|
||||
*/
|
||||
private int findIndex(int offset) {
|
||||
if (myChanges.isEmpty()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
// We assume that document is changed sequentially from start to end, hence, it's worth to perform quick offset comparison with
|
||||
// the last stored change if any
|
||||
TextChangeImpl change = myChanges.get(myChanges.size() - 1);
|
||||
if (offset > change.getStart()) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int start = 0;
|
||||
int end = myChanges.size() - 1;
|
||||
int result = -1;
|
||||
|
||||
// We inline binary search here mainly because TextChange class is immutable and we don't want unnecessary expenses on
|
||||
// new key object construction on every method call.
|
||||
while (start <= end) {
|
||||
result = (end + start) >>> 1;
|
||||
change = myChanges.get(result);
|
||||
if (change.getStart() < offset) {
|
||||
start = ++result;
|
||||
continue;
|
||||
}
|
||||
if (change.getStart() > offset) {
|
||||
end = result - 1;
|
||||
continue;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
-190
@@ -1,190 +0,0 @@
|
||||
package com.intellij.application.options;
|
||||
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.TextChange;
|
||||
import com.intellij.openapi.editor.impl.TextChangeImpl;
|
||||
import com.intellij.openapi.editor.impl.event.DocumentEventImpl;
|
||||
import org.jmock.Expectations;
|
||||
import org.jmock.Mockery;
|
||||
import org.jmock.integration.junit4.JUnit4Mockery;
|
||||
import org.jmock.lib.legacy.ClassImposteriser;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* @author Denis Zhdanov
|
||||
* @since 10/12/2010
|
||||
*/
|
||||
public class DocumentChangesCollectorTest {
|
||||
|
||||
private static final String TEXT = "0123456789abcdefghijklmnopqrstuvwxyz";
|
||||
|
||||
private static long ourCounter;
|
||||
|
||||
private StringBuilder buffer;
|
||||
private DocumentChangesCollector myCollector;
|
||||
private Mockery myMockery;
|
||||
private Document myDocument;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
myCollector = new DocumentChangesCollector();
|
||||
buffer = new StringBuilder(TEXT);
|
||||
myCollector.setCollectChanges(true);
|
||||
myMockery = new JUnit4Mockery() {{
|
||||
setImposteriser(ClassImposteriser.INSTANCE);
|
||||
}};
|
||||
|
||||
myDocument = myMockery.mock(Document.class);
|
||||
|
||||
myMockery.checking(new Expectations() {{
|
||||
allowing(myDocument).getTextLength(); will(returnValue(TEXT.length()));
|
||||
}});
|
||||
}
|
||||
|
||||
@After
|
||||
public void checkExpectations() {
|
||||
myMockery.assertIsSatisfied();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyCollector() {
|
||||
assertNotNull(myCollector.getChanges());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void scatteredChanges() {
|
||||
populate(0, 0, "AB");
|
||||
populate(5, 5, "CD");
|
||||
checkResult(new TextChangeImpl("", 0, 2), new TextChangeImpl("", 5, 7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insertAndReplaceAtEnd() {
|
||||
populate(0, 0, "AB");
|
||||
populate(1, 4, "CD");
|
||||
checkResult(new TextChangeImpl("01", 0, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void insertAndReplaceAtStart() {
|
||||
populate(2, 2, "AB");
|
||||
populate(1, 3, "CD");
|
||||
checkResult(new TextChangeImpl("1", 1, 4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removeAndReplace() {
|
||||
populate(1, 3, "");
|
||||
populate(1, 4, "ABC");
|
||||
checkResult(new TextChangeImpl("12345", 1, 4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removedChangeWithNonStrictBoundaryMatch() {
|
||||
populate(3, 5, "AB");
|
||||
populate(2, 4, "");
|
||||
checkResult(new TextChangeImpl("234", 2, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void removedChangeWithStrictBoundaryMatch() {
|
||||
populate(3, 5, "AB");
|
||||
populate(3, 5, "");
|
||||
checkResult(new TextChangeImpl("34", 3, 3));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cutChangeFromEnd() {
|
||||
populate(3, 3, "ABCD");
|
||||
populate(4, 8, "");
|
||||
checkResult(new TextChangeImpl("3", 3, 4));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cutChangeFromStart() {
|
||||
populate(3, 3, "ABCD");
|
||||
populate(2, 4, "");
|
||||
checkResult(new TextChangeImpl("2", 2, 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cutInTheMiddleOfInsertedText() {
|
||||
populate(3, 3, "ABCDEF");
|
||||
populate(5, 8, "");
|
||||
checkResult(new TextChangeImpl("", 3, 6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cutInTheMiddleOfReplacedText() {
|
||||
populate(3, 9, "ABCDEF");
|
||||
populate(5, 8, "");
|
||||
checkResult(new TextChangeImpl("345678", 3, 6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cutMultipleChanges() {
|
||||
populate(3, 3, "ABC");
|
||||
populate(8, 8, "DEF");
|
||||
populate(5, 9, "");
|
||||
checkResult(new TextChangeImpl("34", 3, 7));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeChangeFromStart() {
|
||||
populate(3, 3, "ABC");
|
||||
populate(4, 4, "DEF");
|
||||
checkResult(new TextChangeImpl("", 3, 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeChangeFromEnd() {
|
||||
populate(3, 3, "AB");
|
||||
populate(1, 4, "CDEF");
|
||||
checkResult(new TextChangeImpl("12", 1, 6));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeMultipleChangesInOne() {
|
||||
populate(3, 3, "AB");
|
||||
populate(9, 9, "CD");
|
||||
populate(5, 9, "EFGH");
|
||||
checkResult(new TextChangeImpl("3456", 3, 11));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mergeChangesSequence() {
|
||||
populate(1, 1, "AB");
|
||||
populate(3, 3, "CD");
|
||||
populate(5, 5, "EFGH");
|
||||
checkResult(new TextChangeImpl("", 1, 9));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void replaceAndInsertAtEnd() {
|
||||
populate(4, 8, "ABCD");
|
||||
populate(5, 5, "EFGHIJKL");
|
||||
checkResult(new TextChangeImpl("4567", 4, 16));
|
||||
}
|
||||
|
||||
private void populate(int start, int end, String newText) {
|
||||
DocumentEventImpl event = new DocumentEventImpl(
|
||||
myDocument, start, buffer.substring(start, end), newText, ++ourCounter, false
|
||||
);
|
||||
buffer.replace(start, end, newText);
|
||||
myCollector.beforeDocumentChange(event);
|
||||
myCollector.documentChanged(event);
|
||||
}
|
||||
|
||||
private void checkResult(TextChange ... expected) {
|
||||
List<? extends TextChange> actual = myCollector.getChanges();
|
||||
assertEquals(asList(expected), actual);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user