Find & Replace

A drop-in find & replace UI for the Compose Text Editor: a ready-made search bar, live-updating match highlighting, next/previous navigation, case, whole-word, and regular expression matching, and replace / replace-all.

**Try it live: ** open the Find demo on Wasm »

implementation("com.darkrockstudios:composetexteditor-find:2.0.0")

Recipe

Create a FindState for your editor's state with rememberFindState, show the FindBar, and wire up the standard Ctrl/Cmd+F shortcut, plus F3 and Ctrl/Cmd+G for the next match, with Modifier.findShortcut:

@Composable
fun EditorWithFind() {
val textState = rememberTextEditorState()
val findState = rememberFindState(textState)
var showFind by remember { mutableStateOf(false) }

Column {
AnimatedVisibility(visible = showFind) {
FindBar(
state = findState,
onClose = { showFind = false },
)
}

TextEditor(
state = textState,
modifier = Modifier
.fillMaxSize()
.findShortcut(findState) { showFind = !showFind },
)
}
}

The highlights update automatically as the user edits the text while a search is active.

Driving search from code

FindState exposes the full feature set if you want to build your own UI:

findState.search("needle")
findState.toggleCaseSensitive(true) // also toggleWholeWord() and toggleRegex(); each re-runs the search
findState.isInvalidPattern // true while a regex query does not compile
findState.toggleInSelection(true) // limit matches to the current selection
findState.findNext() // and findPrevious()
findState.replaceCurrent("thread") // replace the active match, advance to the next
val replaced = findState.replaceAll("thread")
findState.clearSearch()

With regular expressions on, a replacement can refer to the match's groups in the syntax of Kotlin's Regex.replace: $0 for the whole match, $1 and up for numbered groups, ${name} for named ones, and a backslash to make the next character literal (\$, \\). As in most editors, \n and \t insert a line break and a tab. A reference to a group the pattern does not have is inserted as written. With regular expressions off, the replacement is always literal.

A replacement the editor's input filter refuses is not made: replaceCurrent returns false and keeps the match current, and replaceAll counts only what it replaced and leaves the refused matches as the matches.

For a headless, one-shot search with no state object, use TextEditorState.findAll, which returns the matching TextEditorRanges in document order.

Packages

Link copied to clipboard
common

Find & replace for the Compose Text Editor: the FindBar UI, the FindState holder and its rememberFindState factory, the findShortcut modifier, localizable FindBarStrings, and the match highlight styles.