editor: fix incorrect union computation of two TextRange sets for FindInEditor

GitOrigin-RevId: a9741d0071633dca613ec71bb0d2ce26219f25e8
This commit is contained in:
Aleksey Pivovarov
2024-03-14 19:28:39 +00:00
committed by intellij-monorepo-bot
parent 0b696be98d
commit 83d69a1b92
2 changed files with 104 additions and 43 deletions
@@ -24,10 +24,12 @@ import com.intellij.ui.HintHint;
import com.intellij.ui.LightweightHint;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.opentest4j.AssertionFailedError;
import java.awt.*;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.util.Arrays;
public class FindInEditorTest extends LightPlatformCodeInsightTestCase {
private LivePreviewController myLivePreviewController;
@@ -343,4 +345,51 @@ public class FindInEditorTest extends LightPlatformCodeInsightTestCase {
EditorFactory.getInstance().releaseEditor(editor);
}
}
public void testSearchAreaUnion() {
SearchResults.SearchArea emptyArea = new SearchResults.SearchArea(new int[]{}, new int[]{});
SearchResults.SearchArea area1 = new SearchResults.SearchArea(new int[]{10, 90}, new int[]{30, 95});
SearchResults.SearchArea area2 = new SearchResults.SearchArea(new int[]{5, 25}, new int[]{15, 45});
SearchResults.SearchArea area3 = new SearchResults.SearchArea(new int[]{5, 10, 85}, new int[]{6, 12, 90});
assertSameSearchAreas(area1.union(area1), area1);
assertSameSearchAreas(area2.union(area2), area2);
assertSameSearchAreas(area3.union(area3), area3);
assertSameSearchAreas(area1.union(area2), area2.union(area1));
assertSameSearchAreas(area1.union(area3), area3.union(area1));
assertSameSearchAreas(area2.union(area3), area3.union(area2));
assertSameSearchAreas(new SearchResults.SearchArea(new int[]{5, 90}, new int[]{45, 95}),
area1.union(area2));
assertSameSearchAreas(new SearchResults.SearchArea(new int[]{5, 10, 85}, new int[]{6, 30, 95}),
area1.union(area3));
assertSameSearchAreas(new SearchResults.SearchArea(new int[]{5, 25, 85}, new int[]{15, 45, 90}),
area2.union(area3));
assertSameSearchAreas(new SearchResults.SearchArea(new int[]{5, 85}, new int[]{45, 95}),
area1.union(area2).union(area3));
assertSameSearchAreas(emptyArea, emptyArea.union(emptyArea));
assertSameSearchAreas(area1, emptyArea.union(area1));
assertSameSearchAreas(area1, area1.union(emptyArea));
}
private static void assertSameSearchAreas(SearchResults.SearchArea expected,
SearchResults.SearchArea actual) {
if (Arrays.equals(expected.startOffsets(), actual.startOffsets()) &&
Arrays.equals(expected.endOffsets(), actual.endOffsets())) {
return;
}
throw new AssertionFailedError(null,
"startOffsets:\n" +
StringUtil.join(expected.startOffsets(), "\n") +
"\n\nendOffsets:\n" +
StringUtil.join(expected.endOffsets(), "\n"),
"startOffsets:\n" +
StringUtil.join(actual.startOffsets(), "\n") +
"\n\nendOffsets:\n" +
StringUtil.join(actual.endOffsets(), "\n"));
}
}
@@ -7,6 +7,7 @@ import com.intellij.find.FindModel;
import com.intellij.find.FindResult;
import com.intellij.find.FindUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.editor.Caret;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.RangeMarker;
@@ -28,6 +29,8 @@ import com.intellij.util.concurrency.annotations.RequiresEdt;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.Stack;
import com.intellij.util.ui.UIUtil;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntList;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
@@ -275,58 +278,67 @@ public class SearchResults implements DocumentListener, CaretListener {
/**
* Merges the given {@link SearchArea} with the current one.
* <p/>
* If the given areas intersect - union area will be returned.
* If the given areas have no intersection - area contains all the given areas will be returned.
*
* @param area the area to merge with the current
* @return a new merged area
*/
public @NotNull SearchArea merge(@NotNull SearchArea area) {
int[] startOffsets1 = startOffsets;
int[] endOffsets1 = endOffsets;
public @NotNull SearchArea union(@NotNull SearchArea area) {
int[] mergedStartOffsets = ArrayUtil.mergeArrays(startOffsets, area.startOffsets);
int[] mergedEndOffsets = ArrayUtil.mergeArrays(endOffsets, area.endOffsets);
Arrays.sort(mergedStartOffsets);
Arrays.sort(mergedEndOffsets);
int[] startOffsets2 = area.startOffsets;
int[] endOffsets2 = area.endOffsets;
final IntList resultStartOffsets = new IntArrayList(mergedStartOffsets.length);
final IntList resultEndOffsets = new IntArrayList(mergedStartOffsets.length);
List<TextRange> notIntersectedRanges = new ArrayList<>();
new Object() {
int counter = 0;
int startsIndex = 0;
int endsIndex = 0;
for (int i1 = 0; i1 < startOffsets1.length; i1++) {
int startOffset1 = startOffsets1[i1];
int endOffset1 = endOffsets1[i1];
TextRange range1 = new TextRange(startOffset1, endOffset1);
boolean intersects = false;
for (int i2 = 0; i2 < startOffsets2.length; i2++) {
int startOffset2 = startOffsets2[i2];
int endOffset2 = endOffsets2[i2];
TextRange range2 = new TextRange(startOffset2, endOffset2);
if (range1.intersects(range2)) {
TextRange union = range1.union(range2);
startOffsets2[i2] = union.getStartOffset();
endOffsets2[i2] = union.getEndOffset();
intersects = true;
break;
void run() {
while (startsIndex < mergedStartOffsets.length ||
endsIndex < mergedEndOffsets.length) {
if (endsIndex == mergedEndOffsets.length) {
Logger.getInstance(SearchArea.class).error(String.format("Merging invalid SearchArea: %s - %s", this, area));
nextStart();
}
else if (startsIndex == mergedStartOffsets.length) {
nextEnd();
}
else {
int start = mergedStartOffsets[startsIndex];
int end = mergedEndOffsets[endsIndex];
if (start <= end) {
nextStart();
}
else {
nextEnd();
}
}
}
}
if (!intersects) {
notIntersectedRanges.add(range1);
void nextStart() {
if (counter == 0) {
int startOffset = mergedStartOffsets[startsIndex];
resultStartOffsets.add(startOffset);
}
counter++;
startsIndex++;
}
}
int[] notIntersectedStartOffsets = new int[notIntersectedRanges.size()];
int[] notIntersectedEndOffsets = new int[notIntersectedRanges.size()];
for (int i = 0; i < notIntersectedRanges.size(); i++) {
notIntersectedStartOffsets[i] = notIntersectedRanges.get(i).getStartOffset();
notIntersectedEndOffsets[i] = notIntersectedRanges.get(i).getEndOffset();
}
void nextEnd() {
counter--;
if (counter == 0) {
int endOffset = mergedEndOffsets[endsIndex];
resultEndOffsets.add(endOffset);
}
if (counter < 0) {
Logger.getInstance(SearchArea.class).error(String.format("Merging invalid SearchArea: %s - %s", this, area));
}
endsIndex++;
}
}.run();
int[] mergedStartOffsets = ArrayUtil.mergeArrays(notIntersectedStartOffsets, startOffsets2);
int[] mergedEndOffsets = ArrayUtil.mergeArrays(notIntersectedEndOffsets, endOffsets2);
return create(mergedStartOffsets, mergedEndOffsets);
return create(resultStartOffsets.toIntArray(), resultEndOffsets.toIntArray());
}
}
@@ -366,7 +378,7 @@ public class SearchResults implements DocumentListener, CaretListener {
searchArea = searchAreaFromEP;
}
else {
searchArea = searchArea.merge(searchAreaFromEP);
searchArea = searchArea.union(searchAreaFromEP);
}
}