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
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.