void (@NotNull StringBuilder text, @NotNull String replaceFrom, @NotNull String replaceTo, @NotNull List<TextRange> readOnlyChanges) { if (replaceFrom.isEmpty()) { return; } for (int i = text.indexOf(replaceFrom); i >= 0; i = text.indexOf(replaceFrom, i + 1)) { int end = i + replaceFrom.length(); if (intersects(readOnlyChanges, i, end)) { continue; } if (!"\n".equals(replaceFrom)) { if (end < text.length() && !ALLOWED_LINK_SEPARATORS.contains(text.charAt(end))) { // Consider a situation when we have, say, replacement from text 'PsiType' and encounter a 'PsiTypeParameter' in the text. // We don't want to perform the replacement then. continue; } if (i > 0 && !ALLOWED_LINK_SEPARATORS.contains(text.charAt(i - 1))) { // Similar situation but targets head match: from = 'TextRange', text = 'getTextRange()'. continue; } } text.replace(i, end, replaceTo); int diff = replaceTo.length() - replaceFrom.length(); for (int j = 0; j < readOnlyChanges.size(); j++) { TextRange range = readOnlyChanges.get(j); if (range.getStartOffset() >= end) { readOnlyChanges.set(j, range.shiftRight(diff)); } } readOnlyChanges.add(new TextRange(i, i + replaceTo.length())); } }