// returns startOffset found, or -1 if need to continue searching private static int countNewLinesIn(CharSequence text, int[] pos, int line) { int offsetInside = 0; for (int i = StringUtil.indexOf(text, '\n'); i != -1; i = StringUtil.indexOf(text, '\n', offsetInside)) { int curLine = ++pos[0]; int lineLength = i + 1 - offsetInside; int offset = pos[1] += lineLength; offsetInside += lineLength; if (curLine == line) return offset; } pos[1] += text.length() - offsetInside; return -1; }
@Nullable private static String getPropertyName(@NotNull Document document, int line) { int startOffset = document.getLineStartOffset(line); int endOffset = StringUtil.indexOf( document.getCharsSequence(), '=', startOffset, document.getLineEndOffset(line)); if (endOffset <= startOffset) { return null; } String propertyName = document.getCharsSequence().subSequence(startOffset, endOffset).toString().trim(); return propertyName.isEmpty() ? null : propertyName; }
public static boolean isHashBangLine(CharSequence firstCharsIfText, String marker) { if (firstCharsIfText == null) { return false; } final int lineBreak = StringUtil.indexOf(firstCharsIfText, '\n'); if (lineBreak < 0) { return false; } String firstLine = firstCharsIfText.subSequence(0, lineBreak).toString(); if (!firstLine.startsWith("#!")) { return false; } return firstLine.contains(marker); }
public void replace(int initialStart, int length, String replace) { // calculating fragment // minimize replace int start = 0; int end = start + length; final int replaceLength = replace.length(); final String chars = getText(start + initialStart, end + initialStart); if (chars.equals(replace)) return; int newStartInReplace = 0; int newEndInReplace = replaceLength; while (newStartInReplace < replaceLength && start < end && replace.charAt(newStartInReplace) == chars.charAt(start)) { start++; newStartInReplace++; } while (start < end && newStartInReplace < newEndInReplace && replace.charAt(newEndInReplace - 1) == chars.charAt(end - 1)) { newEndInReplace--; end--; } // optimization: when delete fragment from the middle of the text, prefer split at the line // boundaries if (newStartInReplace == newEndInReplace && start > 0 && start < end && StringUtil.indexOf(chars, '\n', start, end) != -1) { // try to align to the line boundaries while (start > 0 && newStartInReplace > 0 && chars.charAt(start - 1) == chars.charAt(end - 1) && chars.charAt(end - 1) != '\n') { start--; end--; newStartInReplace--; newEndInReplace--; } } // [mike] dirty hack for xml: // make sure that deletion of <t> in: <tag><t/><tag> doesn't remove t/>< // which is perfectly valid but invalidates range markers start += initialStart; end += initialStart; final CharSequence charsSequence = myDocument.getCharsSequence(); while (start < charsSequence.length() && end < charsSequence.length() && start > 0 && charsSequence.subSequence(start, end).toString().endsWith("><") && charsSequence.charAt(start - 1) == '<') { start--; newStartInReplace--; end--; newEndInReplace--; } replace = replace.substring(newStartInReplace, newEndInReplace); length = end - start; final Pair<MutableTextRange, StringBuffer> fragment = getFragmentByRange(start, length); final StringBuffer fragmentReplaceText = fragment.getSecond(); final int startInFragment = start - fragment.getFirst().getStartOffset(); // text range adjustment final int lengthDiff = replace.length() - length; final Iterator<Pair<MutableTextRange, StringBuffer>> iterator = myAffectedFragments.iterator(); boolean adjust = false; while (iterator.hasNext()) { final Pair<MutableTextRange, StringBuffer> pair = iterator.next(); if (adjust) pair.getFirst().shift(lengthDiff); if (pair == fragment) adjust = true; } fragmentReplaceText.replace(startInFragment, startInFragment + length, replace); }
public void replace( int psiStart, int length, @NotNull String replace, @Nullable PsiElement replacement) { // calculating fragment // minimize replace int start = 0; int end = start + length; final CharSequence chars = myPsiText.subSequence(psiStart, psiStart + length); if (StringUtil.equals(chars, replace)) return; int newStartInReplace = 0; final int replaceLength = replace.length(); while (newStartInReplace < replaceLength && start < end && replace.charAt(newStartInReplace) == chars.charAt(start)) { start++; newStartInReplace++; } int newEndInReplace = replaceLength; while (start < end && newStartInReplace < newEndInReplace && replace.charAt(newEndInReplace - 1) == chars.charAt(end - 1)) { newEndInReplace--; end--; } // increase the changed range to start and end on PSI token boundaries // this will help to survive smart pointers with the same boundaries if (replacement != null && (newStartInReplace > 0 || newEndInReplace < replaceLength)) { PsiElement startLeaf = replacement.findElementAt(newStartInReplace); PsiElement endLeaf = replacement.findElementAt(newEndInReplace - 1); if (startLeaf != null && endLeaf != null) { int leafStart = startLeaf.getTextRange().getStartOffset() - replacement.getTextRange().getStartOffset(); int leafEnd = endLeaf.getTextRange().getEndOffset() - replacement.getTextRange().getStartOffset(); start += leafStart - newStartInReplace; end += leafEnd - newEndInReplace; newStartInReplace = leafStart; newEndInReplace = leafEnd; } } // optimization: when delete fragment from the middle of the text, prefer split at the line // boundaries if (newStartInReplace == newEndInReplace && start > 0 && start < end && StringUtil.indexOf(chars, '\n', start, end) != -1) { // try to align to the line boundaries while (start > 0 && newStartInReplace > 0 && chars.charAt(start - 1) == chars.charAt(end - 1) && chars.charAt(end - 1) != '\n') { start--; end--; newStartInReplace--; newEndInReplace--; } } start += psiStart; end += psiStart; // [mike] dirty hack for xml: // make sure that deletion of <t> in: <tag><t/><tag> doesn't remove t/>< // which is perfectly valid but invalidates range markers final CharSequence charsSequence = myPsiText; while (start < charsSequence.length() && end < charsSequence.length() && start > 0 && charsSequence.subSequence(start, end).toString().endsWith("><") && charsSequence.charAt(start - 1) == '<') { start--; newStartInReplace--; end--; newEndInReplace--; } updateFragments(start, end, replace.substring(newStartInReplace, newEndInReplace)); }
public int matchingDegree(@NotNull String name) { FList<TextRange> iterable = matchingFragments(name); if (iterable == null) return Integer.MIN_VALUE; if (iterable.isEmpty()) return 0; final TextRange first = iterable.getHead(); boolean startMatch = first.getStartOffset() == 0; int matchingCase = 0; int p = -1; int integral = 0; // -sum of matching-char-count * hump-index over all matched humps; favors longer // fragments matching earlier words int humpIndex = 1; int nextHumpStart = 0; for (TextRange range : iterable) { for (int i = range.getStartOffset(); i < range.getEndOffset(); i++) { boolean isHumpStart = false; while (nextHumpStart <= i) { if (nextHumpStart == i) { isHumpStart = true; } nextHumpStart = NameUtil.nextWord(name, nextHumpStart); if (first != range) { humpIndex++; } } integral -= humpIndex; char c = name.charAt(i); p = StringUtil.indexOf(myPattern, c, p + 1, myPattern.length, false); if (p < 0) { break; } if (c == myPattern[p]) { if (isUpperCase[p]) matchingCase += 50; // strongly prefer user's uppercase matching uppercase: they made an effort to // press Shift else if (i == 0 && startMatch) matchingCase += 15; // the very first letter case distinguishes classes in Java etc else if (isHumpStart) matchingCase += 1; // if a lowercase matches lowercase hump start, that also means something } else if (isHumpStart) { // disfavor hump starts where pattern letter case doesn't match name case matchingCase -= 20; } } } int startIndex = first.getStartOffset(); boolean afterSeparator = StringUtil.indexOfAny(name, HARD_SEPARATORS, 0, startIndex) >= 0; boolean wordStart = startIndex == 0 || isWordStart(name, startIndex) && !isWordStart(name, startIndex - 1); boolean finalMatch = iterable.get(iterable.size() - 1).getEndOffset() == name.length(); return (wordStart ? 1000 : 0) + integral * 10 + matchingCase * (startMatch ? 10 : 1) + // in start matches, case is more important; in middle matches - fragment length // (integral) (afterSeparator ? 0 : 2) + (finalMatch ? 1 : 0); }