Set<TextRange> (@NotNull String s, @NotNull String prefix, @NotNull String suffix, boolean useFullTextRange, boolean filterNestedRanges) { if (!s.contains(prefix)) { return emptySet(); } Set<TextRange> ranges = new LinkedHashSet<>(2); Deque<Integer> prefixes = new ArrayDeque<>(); int searchFrom = 0; while (searchFrom < s.length()) { int nextSuffix = s.indexOf(suffix, searchFrom); if (nextSuffix < 0) { break; // no more suffixes to create ranges } int nextPrefix = s.indexOf(prefix, searchFrom); if (prefixes.isEmpty()) { if (nextPrefix < 0) { break; // no more prefixes to match with suffix } prefixes.push(nextPrefix); searchFrom = nextPrefix + 1; } else if (nextPrefix < 0 || nextSuffix <= nextPrefix || nextPrefix + prefix.length() > nextSuffix) { // support overlapping tokens // suffix first int prefixPairPos = prefixes.pop(); int startOffset = prefixPairPos + (useFullTextRange ? 0 : prefix.length()); int endOffset = useFullTextRange ? nextSuffix + suffix.length() : nextSuffix; ranges.add(TextRange.create(startOffset, endOffset)); // remove overlapping prefix positions from stack while (!prefixes.isEmpty() && prefixes.peek() + prefix.length() > prefixPairPos) { prefixes.pop(); } searchFrom = nextSuffix + suffix.length(); } else { // prefix first prefixes.push(nextPrefix); searchFrom = nextPrefix + 1; } } return filterNestedRanges ? filterNested(ranges) : ranges; }