boolean matchesOldSoftWrap(SoftWrap newSoftWrap, int lengthDiff) { return Collections.binarySearch(myAffectedByUpdateSoftWraps, new SoftWrapImpl(new TextChangeImpl(newSoftWrap.getText(), newSoftWrap.getStart() - lengthDiff, newSoftWrap.getEnd() - lengthDiff), newSoftWrap.getIndentInColumns(), newSoftWrap.getIndentInPixels()), new Comparator<SoftWrapImpl>() { @Override public int compare(SoftWrapImpl o1, SoftWrapImpl o2) { int offsetDiff = o1.getStart() - o2.getStart(); if (offsetDiff != 0) { return offsetDiff; } int textDiff = o1.getText().toString().compareTo(o2.getText().toString()); if (textDiff != 0) { return textDiff; } int colIndentDiff = o1.getIndentInColumns() - o2.getIndentInColumns(); if (colIndentDiff != 0) { return colIndentDiff; } return o1.getIndentInPixels() - o2.getIndentInPixels(); } }) >= 0; }
private void doTest(String initial, String expected, TextChangeImpl ... changes) { if (myConfig.inplace()) { int diff = 0; for (TextChangeImpl change : changes) { diff += change.getDiff(); } int outputUsefulLength = initial.length() + diff; int dataLength = myConfig.dataArrayLength(); if (dataLength < 0) { dataLength = Math.max(outputUsefulLength, initial.length()); } char[] data = new char[dataLength]; System.arraycopy(initial.toCharArray(), 0, data, 0, initial.length()); myMerger.mergeInPlace(data, initial.length(), Arrays.asList(changes)); assertEquals(expected, new String(data, 0, outputUsefulLength)); } else { int interestedSymbolsNumber = myConfig.initialTextLength(); if (interestedSymbolsNumber < 0) { interestedSymbolsNumber = initial.length(); } CharSequence actual = myMerger.mergeToCharSequence(initial.toCharArray(), interestedSymbolsNumber, Arrays.asList(changes)); assertEquals(expected, actual.toString()); } }
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; } } }
boolean matchesOldSoftWrap(SoftWrap newSoftWrap, int lengthDiff) { return Collections.binarySearch(myAffectedByUpdateSoftWraps, new SoftWrapImpl(new TextChangeImpl(newSoftWrap.getText(), newSoftWrap.getStart() - lengthDiff, newSoftWrap.getEnd() - lengthDiff), newSoftWrap.getIndentInColumns(), newSoftWrap.getIndentInPixels()), (o1, o2) -> { int offsetDiff = o1.getStart() - o2.getStart(); if (offsetDiff != 0) { return offsetDiff; } int textDiff = o1.getText().toString().compareTo(o2.getText().toString()); if (textDiff != 0) { return textDiff; } int colIndentDiff = o1.getIndentInColumns() - o2.getIndentInColumns(); if (colIndentDiff != 0) { return colIndentDiff; } return o1.getIndentInPixels() - o2.getIndentInPixels(); }) >= 0; }
@Test public void cutMultipleChanges() { populate(3, 3, "ABC"); populate(8, 8, "DEF"); populate(5, 9, ""); checkResult(new TextChangeImpl("34", 3, 7)); }
@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)); }
public void onSoftWrapEnd() { myStorage.storeOrReplace( new SoftWrapImpl( new TextChangeImpl('\n' + mySoftWrapBuffer.toString(), softWrapStartOffset), mySoftWrapBuffer.length() + 1/* for 'after soft wrap' drawing */, (mySoftWrapBuffer.length() * SPACE_SIZE) + SOFT_WRAP_DRAWING_WIDTH )); mySoftWrapBuffer.setLength(0); insideSoftWrap = false; x += SOFT_WRAP_DRAWING_WIDTH; }
@Test public void propertiesExposing() { int start = 1; int end = 10; String text = "test"; TextChange textChange = new TextChangeImpl(text, start, end); assertTrue(StringUtil.equals(text, textChange.getText())); assertArrayEquals(text.toCharArray(), textChange.getChars()); assertEquals(start, textChange.getStart()); assertEquals(end, textChange.getEnd()); }
@Test public void advance() { TextChangeImpl base = new TextChangeImpl("xyz", 3, 5); int[] offsets = {5, 0, -3}; for (int offset : offsets) { int start = base.getStart(); int end = base.getEnd(); base.advance(offset); assertEquals(new TextChangeImpl(base.getText(), start + offset, end + offset), base); } }
/** * 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); } }
/** * 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; }
public void onSoftWrapEnd() { myStorage.storeOrReplace( new SoftWrapImpl( new TextChangeImpl('\n' + mySoftWrapBuffer.toString(), softWrapStartOffset), mySoftWrapBuffer.length() + 1/* for 'after soft wrap' drawing */, (mySoftWrapBuffer.length() * SPACE_SIZE) + SOFT_WRAP_DRAWING_WIDTH ), false ); mySoftWrapBuffer.setLength(0); insideSoftWrap = false; x += SOFT_WRAP_DRAWING_WIDTH; }
public SoftWrapImpl(@NotNull TextChangeImpl change, int indentInColumns, int indentInPixels) { myChange = change; myIndentInColumns = indentInColumns; myIndentInPixels = indentInPixels; }
public TextChangeImpl getChange() { return myChange; }
@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)); }