public static IntRange getRangeForRestyle(@Nonnull String text,
											  @Nonnull StyleSpans<Collection<String>> styleSpans,
											  @Nullable SyntaxHighlighter syntaxHighlighter,
											  @Nonnull PlainTextChange change) {
		int textLength = text.length();
		if (textLength == 0) return IntRange.EMPTY;

		// Get range of change
		String inserted = change.getInserted();
		String removed = change.getRemoved();
		int changeStart = change.getPosition();
		int changeEnd;
		if (!inserted.isBlank()) {
			changeEnd = change.getInsertionEnd();
		} else if (!removed.isBlank()) {
			changeEnd = change.getRemovalEnd();
		} else {
			changeEnd = changeStart + Math.abs(change.getNetLength());
		}

		// Expand the range to the boundaries of top-level flattened style-spans.
		// If you change a character in the middle of a multi-line comment, this will make the
		// bounds fit from '/*' to '*/'. This way when we pass the range to the styler it operates on a range
		// that previously matched a top-level rule. In most cases it will make the same match again.
		int styleStart;
		int styleEnd;
		List<IntRange> topLevelSpanRanges = SyntaxUtil.flatten(styleSpans);
		try {
			// Find where the change start and end positions would be inserted into the flattened ranges.
			IntRange startMarker = new IntRange(changeStart - 1, changeStart);
			IntRange endMarker = new IntRange(changeEnd - 1, changeEnd);
			int searchEnd = topLevelSpanRanges.size() - 1;
			int startIndex = abs(Lists.binarySearch(topLevelSpanRanges, startMarker));
			int endIndex = abs(Lists.binarySearch(topLevelSpanRanges, endMarker, startIndex, searchEnd));

			// Normalize binary search results.
			int size = topLevelSpanRanges.size() - 1;
			startIndex = min(size, max(0, startIndex));
			endIndex = min(size, max(0, endIndex));

			// Map to positions in the text.
			styleStart = topLevelSpanRanges.get(startIndex).start();
			styleEnd = topLevelSpanRanges.get(endIndex).end();
		} catch (Exception ex) {
			logger.error("Failed to get span range for change", ex);
			throw new IllegalStateException(ex);
		}

		// Fit to paragraph start/end (newline boundaries)
		styleStart = Math.min(styleStart, text.substring(0, styleStart).lastIndexOf('\n') + 1);
		styleEnd = Math.max(styleEnd, text.indexOf('\n', styleEnd - 1));

		// Allow the syntax highlighter implementation to reshape the range.
		// Building on the example from before with multi-line comments, lets say you delete the last `/` that
		// closes a multi-line comment. The existing range up until here will cut off at the end of the prior range
		// of the multi-line comment. But now that the comment is open, it will continue until the next '*/'.
		//
		// Because of this, we let the syntax highlighter implementations expand the range if they have constructs
		// like multi-line comments.
		if (syntaxHighlighter != null) {
			IntRange expandedRange = syntaxHighlighter.expandRange(text, styleStart, styleEnd);
			styleStart = Math.min(styleStart, expandedRange.start());
			styleEnd = Math.max(styleEnd, expandedRange.end());

			// Fit to paragraph start/end (newline boundaries) again
			styleStart = Math.min(styleStart, text.substring(0, styleStart).lastIndexOf('\n') + 1);
			styleEnd = Math.max(styleEnd, text.indexOf('\n', styleEnd - 1));
		}

		return new IntRange(styleStart, styleEnd);
	}
@param text
		Full document text.
@param styleSpans
		Existing style-spans for the full document text.
@param syntaxHighlighter
		Existing highlighter to use for {@link SyntaxHighlighter#expandRange(String, int, int)}.
		May be {@code null} to skip this process.
@param change
		The change made in the editor's text.

@return Range to restyle in order to fit the changed text.
recaf-ui/src/main/java/software/coley/recaf/ui/control/richtext/syntax/SyntaxUtil.java