From eb94c86d0d3d95b9fa5d7201fd3ad4e3e1a9f608 Mon Sep 17 00:00:00 2001 From: Piotr Tomiak Date: Tue, 21 Jan 2020 10:22:12 +0100 Subject: [PATCH] WEB-43327 React.js: "Expand selection" works incorrectly in Redirect tag expression - improve code readability IDEA-CR-57481 GitOrigin-RevId: 1fe3993dfde741666dd102255e29065c3c5c37aa --- .../InjectedFileReferenceSelectioner.java | 70 ++++++++++--------- 1 file changed, 38 insertions(+), 32 deletions(-) diff --git a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/wordSelection/InjectedFileReferenceSelectioner.java b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/wordSelection/InjectedFileReferenceSelectioner.java index c8276ffa391c..88ac0c3d8ecd 100644 --- a/platform/lang-impl/src/com/intellij/codeInsight/editorActions/wordSelection/InjectedFileReferenceSelectioner.java +++ b/platform/lang-impl/src/com/intellij/codeInsight/editorActions/wordSelection/InjectedFileReferenceSelectioner.java @@ -19,6 +19,9 @@ import one.util.streamex.StreamEx; import org.jetbrains.annotations.NotNull; import java.util.*; +import java.util.stream.IntStream; + +import static com.intellij.util.ObjectUtils.notNull; /** * This selectioner tries to guess location of file segments within a particular element. @@ -44,18 +47,14 @@ public class InjectedFileReferenceSelectioner extends AbstractWordSelectioner { PsiElement valueElement = findValueElement(host, realRange); - List nonLeafChildren = StreamEx.of(valueElement.getChildren()) - .filter(child -> !(child instanceof LeafPsiElement)) - .map(PsiElement::getTextRange) - .sorted(Comparator.comparing(TextRange::getStartOffset).reversed()) - .toList(); - realRange = limitToCurrentLineAndStripWhiteSpace(editorText, cursorOffset, realRange); - Set charEscapeLocations = isWithinLiteral(e, host) ? findCharEscapeLocations(editor, editorText, host.getTextRange()) - : Collections.emptySet(); + BitSet charEscapeLocations = isWithinLiteral(e, host) + ? findCharEscapeLocations(editor, editorText, host.getTextRange(), realRange.getStartOffset()) + : new BitSet(0); - List segments = buildSegments(editorText, cursorOffset, charEscapeLocations, realRange, nonLeafChildren); + BitSet compositeIndexes = createCompositeIndexesSet(valueElement, realRange.getStartOffset()); + List segments = buildSegments(editorText, cursorOffset, realRange, charEscapeLocations, compositeIndexes); if (!segments.isEmpty()) { int endOffsetAlignment = segments.get(segments.size() - 1).getEndOffset(); for (ListIterator it = segments.listIterator(); it.hasNext(); ) { @@ -70,19 +69,19 @@ public class InjectedFileReferenceSelectioner extends AbstractWordSelectioner { @NotNull private static PsiElement findValueElement(@NotNull PsiElement host, @NotNull TextRange valueRange) { - PsiElement result = host.getContainingFile().findElementAt(valueRange.getStartOffset()); - while (result != null && result != host && !result.getTextRange().contains(valueRange)) { - result = result.getParent(); - } - return result != null ? result : host; + return notNull( + PsiTreeUtil.findFirstParent( + host.getContainingFile().findElementAt(valueRange.getStartOffset()), false, + parent -> parent == host || parent.getTextRange().contains(valueRange)), + host); } @NotNull private static List buildSegments(@NotNull CharSequence editorText, final int cursorOffset, - @NotNull Set charEscapeLocations, @NotNull TextRange range, - List nonLeafChildren) { + @NotNull BitSet charEscapeLocations, + @NotNull BitSet compositeIndexes) { if (range.getLength() == 0) { return Collections.emptyList(); } @@ -94,11 +93,14 @@ public class InjectedFileReferenceSelectioner extends AbstractWordSelectioner { boolean segmentsFinished = false; int hardSegmentCount = 0; - for (int i = hostTextOffset; i < hostTextEndOffset; i = findNextIndexInLeafElement(i, nonLeafChildren)) { + for (int i = hostTextOffset; i < hostTextEndOffset; i++) { + if (compositeIndexes.get(i - hostTextOffset)) { + continue; + } char ch = editorText.charAt(i); if (!segmentsFinished) { if (ch == '/' - || (ch == '\\' && !charEscapeLocations.contains(i)) + || (ch == '\\' && !charEscapeLocations.get(i - hostTextOffset)) //treat space as soft segment marker || (ch == ' ' && i <= cursorOffset)) { if (rangeStart < i) { @@ -120,7 +122,7 @@ public class InjectedFileReferenceSelectioner extends AbstractWordSelectioner { // URLs - expand to content after '?' first, but count it as soft segment else if (ch == '?') { segments.add(new TextRange(rangeStart, i)); - if (i + 1< hostTextEndOffset) { + if (i + 1 < hostTextEndOffset) { segments.add(new TextRange(i + 1, hostTextEndOffset)); } segments.add(new TextRange(rangeStart, hostTextEndOffset)); @@ -142,15 +144,15 @@ public class InjectedFileReferenceSelectioner extends AbstractWordSelectioner { return segments; } - private static int findNextIndexInLeafElement(int currentIndex, List nonLeafChildren) { - // skip any non leaf children - ranges sorted backwards to optimize element removal - while (!nonLeafChildren.isEmpty() && nonLeafChildren.get(nonLeafChildren.size() - 1).getStartOffset() <= currentIndex) { - TextRange nonLeafRange = nonLeafChildren.remove(nonLeafChildren.size() - 1); - if (nonLeafRange.getEndOffset() - 1 > currentIndex) { - return nonLeafRange.getEndOffset(); - } - } - return currentIndex + 1; + @NotNull + private static BitSet createCompositeIndexesSet(@NotNull PsiElement valueElement, int indexesOffset) { + return StreamEx.of(valueElement.getChildren()) + .filter(child -> !(child instanceof LeafPsiElement)) + .map(PsiElement::getTextRange) + .flatMapToInt(range -> IntStream.range(range.getStartOffset(), range.getEndOffset())) + .map(index -> index - indexesOffset) + .atLeast(0) + .toBitSet(); } @NotNull @@ -178,17 +180,21 @@ public class InjectedFileReferenceSelectioner extends AbstractWordSelectioner { || SkipAutopopupInStrings.isInStringLiteral(e); } - private static Set findCharEscapeLocations(@NotNull Editor editor, @NotNull CharSequence text, @NotNull TextRange range) { + private static BitSet findCharEscapeLocations(@NotNull Editor editor, + @NotNull CharSequence text, + @NotNull TextRange range, + int indexesOffset) { HighlighterIterator iterator = ((EditorEx)editor).getHighlighter().createIterator(range.getStartOffset()); int rangeEnd = range.getEndOffset(); - Set locations = new HashSet<>(); + BitSet locations = new BitSet(range.getLength()); int pos; while (!iterator.atEnd() && (pos = iterator.getStart()) < rangeEnd) { if (text.charAt(pos) == '\\' - && (pos + 1 >= rangeEnd || text.charAt(pos + 1) != '\\')) { - locations.add(pos); + && (pos + 1 >= rangeEnd || text.charAt(pos + 1) != '\\') + && pos >= indexesOffset) { + locations.set(pos - indexesOffset); } iterator.advance(); }