# TextFieldBuffer

Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+class:androidx.compose.foundation.text.input.TextFieldBuffer) Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0) Common/All
- Cmn

  ```
  class TextFieldBuffer : Appendable
  ```

*** ** * ** ***

A text buffer that can be edited, similar to `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/-string-builder/index.html`.

This class provides methods for changing the text, such as:

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)`

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#append(kotlin.CharSequence)`

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)`

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)`

This class also stores and tracks the cursor position or selection range. The cursor position is just a selection range with zero length. The cursor and selection can be changed using methods such as:

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorAfterCharAt(kotlin.Int)`

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorBeforeCharAt(kotlin.Int)`

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).placeCursorAtEnd()`

- `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).selectAll()`

To get one of these, and for usage samples, see `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldState#edit(kotlin.Function1)`. Every change to the buffer is tracked in a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.ChangeList` which you can access via the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#changes()` property.

## Summary

| ### Nested types |
|---|
| `interface https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.ChangeList` The ordered list of non-overlapping and discontinuous changes performed on a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` during the current `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldState#edit(kotlin.Function1)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/InputTransformation#(androidx.compose.foundation.text.input.TextFieldBuffer).transformInput()` operation. |

| ### Public functions |
|---|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle>` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)( paragraphStyle: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle, range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange, expandPolicy: https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy )` Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` to the text within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` on this buffer. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)(paragraphStyle: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle, start: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, end: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html)` Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` to the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` on this buffer. | Cmn |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle>` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)( spanStyle: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle, range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange, expandPolicy: https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy )` Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` to the text within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` on this buffer. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)(spanStyle: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle, start: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, end: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html)` Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` to the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` on this buffer. | Cmn |
| `open https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/-appendable/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#append(kotlin.Char)(char: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char/index.html)` | Cmn |
| `open https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/-appendable/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#append(kotlin.CharSequence)(text: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html?)` | Cmn |
| `open https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.text/-appendable/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#append(kotlin.CharSequence,kotlin.Int,kotlin.Int)(text: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html?, start: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, end: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html)` | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#asCharSequence()()` Returns a `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html` backed by this buffer. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#charAt(kotlin.Int)(index: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html)` Returns the `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char/index.html` at `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#charAt(kotlin.Int)` in this buffer. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/-list/index.html<https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle>>` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#getParagraphStyles(androidx.compose.ui.text.TextRange)(range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange)` Returns the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle`s that intersect with the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#getParagraphStyles(androidx.compose.ui.text.TextRange)`. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/-list/index.html<https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle>>` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#getSpanStyles(androidx.compose.ui.text.TextRange)(range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange)` Returns the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle`s that intersect with the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#getSpanStyles(androidx.compose.ui.text.TextRange)`. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorAfterCharAt(kotlin.Int)(index: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html)` Places the cursor after the character at the given index. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorBeforeCharAt(kotlin.Int)(index: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html)` Places the cursor before the character at the given index. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#removeStyle(androidx.compose.foundation.text.input.TrackedRange)(trackedRange: https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<*>)` Removes the exact style represented by the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#removeStyle(androidx.compose.foundation.text.input.TrackedRange)` from this buffer. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)(start: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, end: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, text: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html)` Replaces the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)` (inclusive) and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)` (exclusive) in this value with `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)`, and records the change in `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#changes()`. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#revertAllChanges()()` Revert all changes made to this value since it was created. | Cmn |
| `open https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-string/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#toString()()` | Cmn |

| ### Public properties |
|---|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.ChangeList` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#changes()` The `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.ChangeList` represents the changes made to this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` and is inherently mutable. | Cmn |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<*>.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TrackedRange).expandPolicy()` The `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy` defining how the style range expands when text is inserted at its boundaries. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#hasSelection()` True if the selection range has non-zero length. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<*>.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TrackedRange).isValid()` Whether this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` is still valid in the buffer. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()` The number of characters in the text field. | Cmn |
| `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#originalSelection()` Original selection before the changes. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#originalText()` Original text content of the buffer before any changes were applied. | Cmn |
| `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle>.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TrackedRange).paragraphStyle()` The `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` object associated with this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange`. | Cmn |
| `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#selection()` The selected range of characters. | Cmn |
| `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle>.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TrackedRange).spanStyle()` The `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` object associated with this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange`. | Cmn |
| `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<*>.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TrackedRange).textRange()` The `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` of this style. | Cmn |

| ### Extension functions |
|---|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)(start: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, end: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html)` Delete the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` (inclusive) and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` (exclusive). | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)(index: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html, text: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-string/index.html)` Insert `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` at the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` in this value. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TextFieldBuffer).placeCursorAtEnd()()` Places the cursor at the end of the text. | Cmn |
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-unit/index.html` | `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#(androidx.compose.foundation.text.input.TextFieldBuffer).selectAll()()` Places the selection around all the text. | Cmn |

## Public functions

### addStyle

Cmn Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
fun addStyle(
    paragraphStyle: ParagraphStyle,
    range: TextRange,
    expandPolicy: ExpandPolicy
): TrackedRange<ParagraphStyle>
```

Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` to the text within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` on this buffer.

Styles are applied in the order they are added to the buffer. This order is preserved even as the text is edited and style ranges are adjusted.

Note that the same `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` object can be added to multiple distinct ranges. Each call to `addStyle` creates a new, independent style range, and returns a unique `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` to identify it. This allows you to save object allocations by applying the same style object to different distinct ranges.

| Parameters |
|---|---|
| `paragraphStyle: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` | the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` to be applied |
| `range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` | the effective range of the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` |
| `expandPolicy: https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy` | the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy` defining how the style range expands when text is inserted at its boundaries. |

| Returns |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle>` | a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` referencing the added style. |

| Throws |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-illegal-argument-exception/index.html` | if `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` is out of 0, length, or if it's reversed. |

### addStyle

Cmn Added in [1.9.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.9.0)

```
fun addStyle(paragraphStyle: ParagraphStyle, start: Int, end: Int): Unit
```

Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` to the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` on this buffer.

Styles are applied in the order they are added to the buffer. This order is preserved even as the text is edited and style ranges are adjusted.

Note that the same `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` object can be added to multiple distinct ranges. Each call to `addStyle` creates a new, independent style range. This allows you to save object allocations by applying the same style object to different distinct ranges.

If `https://developer.android.com/reference/kotlin/androidx/compose/foundation/ComposeFoundationFlags#isBasicTextFieldStyledTextEnabled()` is enabled, this function can be called from any `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` scope (such as `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/InputTransformation` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldState#edit(kotlin.Function1)`). The added style will be tracked and its range will automatically adjust as the text is edited, behaving as if called with `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy#AtEnd()`.

If the flag is disabled, this function is only permitted within an `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/OutputTransformation`. Any styling added in this mode will not become part of the underlying text state, nor will its boundaries be tracked or updated during subsequent edits. For predictable results when the flag is disabled, it is recommended to apply styles only after the text content has been fully determined.

| Throws |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-illegal-argument-exception/index.html` | if `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)` is out of range, or if `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)`\>`https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.ParagraphStyle,kotlin.Int,kotlin.Int)`. |

### addStyle

Cmn Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
fun addStyle(
    spanStyle: SpanStyle,
    range: TextRange,
    expandPolicy: ExpandPolicy
): TrackedRange<SpanStyle>
```

Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` to the text within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` on this buffer.

Styles are applied in the order they are added to the buffer. This order is preserved even as the text is edited and style ranges are adjusted.

Note that the same `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` object can be added to multiple distinct ranges. Each call to `addStyle` creates a new, independent style range, and returns a unique `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` to identify it. This allows you to save object allocations by applying the same style object to different distinct ranges.

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight

// This sample demonstrates how to use the `TrackedRange` API to track and modify text ranges
// dynamically. It implements a basic Markdown-like behavior where text typed inside double
// asterisks (e.g., **bold**) is automatically bolded, and the asterisks are removed.
val state = rememberTextFieldState("")

fun IntRange.toTextRange(): TextRange {
    // Unlike IntRange, TextRange is exclusive at the end.
    return TextRange(first, last + 1)
}

val inputTransformation = remember {
    InputTransformation {
        val text = asCharSequence().toString()
        val matches = "\\*\\*([^*]+)\\*\\*".toRegex().findAll(text).toList()
        matches
            .map { match ->
                val contentRange = match.groups[0]!!.range
                // Apply bold style to the text inside asterisks (including the
                // asterisks for now).
                addStyle(
                    SpanStyle(fontWeight = FontWeight.Bold),
                    contentRange.toTextRange(),
                    ExpandPolicy.InsideOnly,
                )
            }
            .forEach { trackedRange ->
                // Remove the asterisks here.

                // `trackedRange` simplifies this logic: normally, deleting characters at
                // the start would shift the end index. However, because `trackedRange`
                // automatically tracks text updates and adjusts its offsets
                // dynamically, we can safely delete the target range without having to
                // calculate the offset manually.
                delete(trackedRange.textRange.start, trackedRange.textRange.start + 2)
                delete(trackedRange.textRange.end - 2, trackedRange.textRange.end)
            }
    }
}

Column {
    Text("Type **text** below to automatically bold it.")

    BasicTextField(
        state = state,
        textStyle = LocalTextStyle.current,
        inputTransformation = inputTransformation,
    )
}
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// This sample demonstrates a realistic rich-text editor scenario using the `TrackedRange` and
// `TextFieldTextStyles` APIs. It implements a "Toggle Bold" formatting function on the current
// selection.

// For simplicity, this sample keeps bold styles non-overlapping and contiguous, assuming they
// are
// applied exclusively through this method.
val state = rememberTextFieldState("Hello World")

// This derived state calculates whether the current selection is completely covered by
// bold text styles. This ensures the "Bold" toggle button accurately reflects the
// state of the selected text.
val isSelection100PercentBold by remember {
    derivedStateOf {
        val selection = state.selection
        if (selection.collapsed) {
            false
        } else {
            val spanStyles = state.textStyles.getSpanStyles(selection)
            var boldCoverage = 0
            for (style in spanStyles) {
                if (style.item.fontWeight == FontWeight.Bold) {
                    val overlapStart = maxOf(style.start, selection.min)
                    val overlapEnd = minOf(style.end, selection.max)
                    if (overlapEnd > overlapStart) {
                        boldCoverage += (overlapEnd - overlapStart)
                    }
                }
            }
            boldCoverage == selection.length
        }
    }
}

fun TextFieldBuffer.unBoldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // We modify or remove existing styles to exclude the selected range
    for (style in intersectingStyles) {
        val range = style.textRange
        if (range.start >= selection.min && range.end <= selection.max) {
            // The style is fully inside the selection. Remove it.
            removeStyle(style)
        } else if (range.start < selection.min && range.end > selection.max) {
            // The style completely covers the selection. We need to split it.
            val oldEnd = range.end
            // Truncate the start part
            style.textRange = TextRange(range.start, selection.min)
            // Add a new style for the end part
            addStyle(
                SpanStyle(fontWeight = FontWeight.Bold),
                TextRange(selection.max, oldEnd),
                ExpandPolicy.AtEnd,
            )
        } else if (range.start < selection.min) {
            // The style overlaps with the start of the selection. Truncate it.
            style.textRange = TextRange(range.start, selection.min)
        } else {
            // The style overlaps with the end of the selection. Truncate it.
            style.textRange = TextRange(selection.max, range.end)
        }
    }
}

fun TextFieldBuffer.boldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // To keep bold styles non-overlapping, we merge any intersecting bold
    // styles with the new selection range into a single contiguous bold style.
    var mergedStart = selection.min
    var mergedEnd = selection.max

    for (style in intersectingStyles) {
        mergedStart = minOf(mergedStart, style.textRange.start)
        mergedEnd = maxOf(mergedEnd, style.textRange.end)
        // Remove the fragmented style
        removeStyle(style)
    }

    addStyle(
        SpanStyle(fontWeight = FontWeight.Bold),
        TextRange(mergedStart, mergedEnd),
        ExpandPolicy.AtEnd,
    )
}

Column {
    Button(
        onClick = {
            state.edit {
                val selection = this.selection
                if (selection.collapsed) return@edit
                if (isSelection100PercentBold) {
                    unBoldSelection()
                } else {
                    boldSelection()
                }
            }
        }
    ) {
        Text(
            "B",
            fontWeight = if (isSelection100PercentBold) FontWeight.Bold else FontWeight.Normal,
        )
    }

    BasicTextField(state = state, textStyle = LocalTextStyle.current)
}
```

```kotlin
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.Text
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// Wipe the bold style on a given range using the TrackedRange.textRange API
val state = TextFieldState("Hello World")

state.edit {
    // Assume we want to "wipe" all bold styles from the first 5 characters.
    val rangeToWipe = TextRange(0, 5)

    // Get all span styles that intersect with the wipe range.
    getSpanStyles(rangeToWipe).forEach { trackedRange ->
        if (trackedRange.spanStyle.fontWeight == FontWeight.Bold) {
            val current = trackedRange.textRange

            if (rangeToWipe.start <= current.start && current.end <= rangeToWipe.end) {
                // Case 1: The bold style is entirely within the wipe range, remove it.
                removeStyle(trackedRange)
            } else if (current.start < rangeToWipe.start && rangeToWipe.end < current.end) {
                // Case 2: The wipe range is in the middle: split the style into two parts.
                val oldEnd = current.end
                // Truncate the original style to end at the start of the wipe range.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
                // Add a new bold style starting after the wipe range.
                addStyle(trackedRange.spanStyle, rangeToWipe.end, oldEnd)
            } else if (current.start < rangeToWipe.start) {
                // Case 3: Overlap at the start of wipe: truncate the style's end.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
            } else {
                // Case 4: Overlap at the end of wipe: truncate the style's start.
                trackedRange.textRange = TextRange(rangeToWipe.end, current.end)
            }
        }
    }
}
```

| Parameters |
|---|---|
| `spanStyle: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` | the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` to be applied |
| `range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` | the effective range of the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` |
| `expandPolicy: https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy` | the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy` defining how the style range expands when text is inserted at its boundaries. |

| Returns |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle>` | a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` referencing the added style. |

| Throws |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-illegal-argument-exception/index.html` | if `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,androidx.compose.ui.text.TextRange,androidx.compose.foundation.text.input.ExpandPolicy)` is out of 0, length, or if it's reversed. |

### addStyle

Cmn Added in [1.9.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.9.0)

```
fun addStyle(spanStyle: SpanStyle, start: Int, end: Int): Unit
```

Adds the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` to the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` on this buffer.

Styles are applied in the order they are added to the buffer. This order is preserved even as the text is edited and style ranges are adjusted.

Note that the same `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` object can be added to multiple distinct ranges. Each call to `addStyle` creates a new, independent style range. This allows you to save object allocations by applying the same style object to different distinct ranges.

If `https://developer.android.com/reference/kotlin/androidx/compose/foundation/ComposeFoundationFlags#isBasicTextFieldStyledTextEnabled()` is enabled, this function can be called from any `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` scope (such as `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/InputTransformation` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldState#edit(kotlin.Function1)`). The added style will be tracked and its range will automatically adjust as the text is edited, behaving as if called with `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy#AtEnd()`.

If the flag is disabled, this function is only permitted within an `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/OutputTransformation`. Any styling added in this mode will not become part of the underlying text state, nor will its boundaries be tracked or updated during subsequent edits. For predictable results when the flag is disabled, it is recommended to apply styles only after the text content has been fully determined.

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight

// This sample demonstrates how to use the `TrackedRange` API to track and modify text ranges
// dynamically. It implements a basic Markdown-like behavior where text typed inside double
// asterisks (e.g., **bold**) is automatically bolded, and the asterisks are removed.
val state = rememberTextFieldState("")

fun IntRange.toTextRange(): TextRange {
    // Unlike IntRange, TextRange is exclusive at the end.
    return TextRange(first, last + 1)
}

val inputTransformation = remember {
    InputTransformation {
        val text = asCharSequence().toString()
        val matches = "\\*\\*([^*]+)\\*\\*".toRegex().findAll(text).toList()
        matches
            .map { match ->
                val contentRange = match.groups[0]!!.range
                // Apply bold style to the text inside asterisks (including the
                // asterisks for now).
                addStyle(
                    SpanStyle(fontWeight = FontWeight.Bold),
                    contentRange.toTextRange(),
                    ExpandPolicy.InsideOnly,
                )
            }
            .forEach { trackedRange ->
                // Remove the asterisks here.

                // `trackedRange` simplifies this logic: normally, deleting characters at
                // the start would shift the end index. However, because `trackedRange`
                // automatically tracks text updates and adjusts its offsets
                // dynamically, we can safely delete the target range without having to
                // calculate the offset manually.
                delete(trackedRange.textRange.start, trackedRange.textRange.start + 2)
                delete(trackedRange.textRange.end - 2, trackedRange.textRange.end)
            }
    }
}

Column {
    Text("Type **text** below to automatically bold it.")

    BasicTextField(
        state = state,
        textStyle = LocalTextStyle.current,
        inputTransformation = inputTransformation,
    )
}
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// This sample demonstrates a realistic rich-text editor scenario using the `TrackedRange` and
// `TextFieldTextStyles` APIs. It implements a "Toggle Bold" formatting function on the current
// selection.

// For simplicity, this sample keeps bold styles non-overlapping and contiguous, assuming they
// are
// applied exclusively through this method.
val state = rememberTextFieldState("Hello World")

// This derived state calculates whether the current selection is completely covered by
// bold text styles. This ensures the "Bold" toggle button accurately reflects the
// state of the selected text.
val isSelection100PercentBold by remember {
    derivedStateOf {
        val selection = state.selection
        if (selection.collapsed) {
            false
        } else {
            val spanStyles = state.textStyles.getSpanStyles(selection)
            var boldCoverage = 0
            for (style in spanStyles) {
                if (style.item.fontWeight == FontWeight.Bold) {
                    val overlapStart = maxOf(style.start, selection.min)
                    val overlapEnd = minOf(style.end, selection.max)
                    if (overlapEnd > overlapStart) {
                        boldCoverage += (overlapEnd - overlapStart)
                    }
                }
            }
            boldCoverage == selection.length
        }
    }
}

fun TextFieldBuffer.unBoldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // We modify or remove existing styles to exclude the selected range
    for (style in intersectingStyles) {
        val range = style.textRange
        if (range.start >= selection.min && range.end <= selection.max) {
            // The style is fully inside the selection. Remove it.
            removeStyle(style)
        } else if (range.start < selection.min && range.end > selection.max) {
            // The style completely covers the selection. We need to split it.
            val oldEnd = range.end
            // Truncate the start part
            style.textRange = TextRange(range.start, selection.min)
            // Add a new style for the end part
            addStyle(
                SpanStyle(fontWeight = FontWeight.Bold),
                TextRange(selection.max, oldEnd),
                ExpandPolicy.AtEnd,
            )
        } else if (range.start < selection.min) {
            // The style overlaps with the start of the selection. Truncate it.
            style.textRange = TextRange(range.start, selection.min)
        } else {
            // The style overlaps with the end of the selection. Truncate it.
            style.textRange = TextRange(selection.max, range.end)
        }
    }
}

fun TextFieldBuffer.boldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // To keep bold styles non-overlapping, we merge any intersecting bold
    // styles with the new selection range into a single contiguous bold style.
    var mergedStart = selection.min
    var mergedEnd = selection.max

    for (style in intersectingStyles) {
        mergedStart = minOf(mergedStart, style.textRange.start)
        mergedEnd = maxOf(mergedEnd, style.textRange.end)
        // Remove the fragmented style
        removeStyle(style)
    }

    addStyle(
        SpanStyle(fontWeight = FontWeight.Bold),
        TextRange(mergedStart, mergedEnd),
        ExpandPolicy.AtEnd,
    )
}

Column {
    Button(
        onClick = {
            state.edit {
                val selection = this.selection
                if (selection.collapsed) return@edit
                if (isSelection100PercentBold) {
                    unBoldSelection()
                } else {
                    boldSelection()
                }
            }
        }
    ) {
        Text(
            "B",
            fontWeight = if (isSelection100PercentBold) FontWeight.Bold else FontWeight.Normal,
        )
    }

    BasicTextField(state = state, textStyle = LocalTextStyle.current)
}
```

| Throws |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-illegal-argument-exception/index.html` | if `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` or `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)` is out of range, or if `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)`\>`https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#addStyle(androidx.compose.ui.text.SpanStyle,kotlin.Int,kotlin.Int)`. |

### append

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
open fun append(char: Char): Appendable
```

### append

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
open fun append(text: CharSequence?): Appendable
```

### append

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
open fun append(text: CharSequence?, start: Int, end: Int): Appendable
```

### asCharSequence

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
fun asCharSequence(): CharSequence
```

Returns a `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html` backed by this buffer. Any subsequent changes to this buffer will be visible in the returned sequence as well.

### charAt

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
fun charAt(index: Int): Char
```

Returns the `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char/index.html` at `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#charAt(kotlin.Int)` in this buffer.

### getParagraphStyles

Cmn Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
fun getParagraphStyles(range: TextRange): List<TrackedRange<ParagraphStyle>>
```

Returns the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle`s that intersect with the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#getParagraphStyles(androidx.compose.ui.text.TextRange)`.

Styles are returned in the same order they were originally added to the buffer.

A style intersects with the range if it overlaps with it at any point. For non-empty ranges, this means `style.start < range.max` and `range.min < style.end`.

Example Query Range: `[5, 15)`

```kotlin
0    5    10   15   20   25
|---|---|---|---|---|
     [---)              Query Range [5, 15)

[---)         Style [0, 20) (Contains query) -> Returned
          [---)              Style [8, 12) (Inside query)   -> Returned
[---)                  Style [0, 10) (Overlap start)  -> Returned
[---)                        Style [0, 5)  (Touching start) -> NOT Returned
               [---)   Style [15, 25)(Touching end)   -> NOT Returned
```

Example Collapsed Query: `[10, 10)`

```kotlin
0    5    10   15   20   25
|---|---|---|---|---|
          |                   Query Range [10, 10)

[---)         Style [0, 20) (Contains query) -> Returned
[---)                  Style [0, 10) (Touching end)   -> NOT Returned
          [---)        Style [10, 20)(Touching start) -> Returned
```

| Parameters |
|---|---|
| `range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` | the range to query |

| Returns |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/-list/index.html<https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle>>` | a list of `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange`s referencing the styles intersecting with the given range, returned in the order they were added to the buffer. |

### getSpanStyles

Cmn Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
fun getSpanStyles(range: TextRange): List<TrackedRange<SpanStyle>>
```

Returns the `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle`s that intersect with the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#getSpanStyles(androidx.compose.ui.text.TextRange)`.

Styles are returned in the same order they were originally added to the buffer.

A style intersects with the range if it overlaps with it at any point. For non-empty ranges, this means `style.start < range.max` and `range.min < style.end`.

Example Query Range: `[5, 15)`

```kotlin
0    5    10   15   20   25
|---|---|---|---|---|
     [---)              Query Range [5, 15)

[---)         Style [0, 20) (Contains query) -> Returned
          [---)              Style [8, 12) (Inside query)   -> Returned
[---)                  Style [0, 10) (Overlap start)  -> Returned
[---)                        Style [0, 5)  (Touching start) -> NOT Returned
               [---)   Style [15, 25)(Touching end)   -> NOT Returned
```

Example Collapsed Query: `[10, 10)`

```kotlin
0    5    10   15   20   25
|---|---|---|---|---|
          |                   Query Range [10, 10)

[---)         Style [0, 20) (Contains query) -> Returned
[---)                  Style [0, 10) (Touching end)   -> NOT Returned
          [---)        Style [10, 20)(Touching start) -> Returned
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight

// This sample demonstrates how to use the `TrackedRange` API to track and modify text ranges
// dynamically. It implements a basic Markdown-like behavior where text typed inside double
// asterisks (e.g., **bold**) is automatically bolded, and the asterisks are removed.
val state = rememberTextFieldState("")

fun IntRange.toTextRange(): TextRange {
    // Unlike IntRange, TextRange is exclusive at the end.
    return TextRange(first, last + 1)
}

val inputTransformation = remember {
    InputTransformation {
        val text = asCharSequence().toString()
        val matches = "\\*\\*([^*]+)\\*\\*".toRegex().findAll(text).toList()
        matches
            .map { match ->
                val contentRange = match.groups[0]!!.range
                // Apply bold style to the text inside asterisks (including the
                // asterisks for now).
                addStyle(
                    SpanStyle(fontWeight = FontWeight.Bold),
                    contentRange.toTextRange(),
                    ExpandPolicy.InsideOnly,
                )
            }
            .forEach { trackedRange ->
                // Remove the asterisks here.

                // `trackedRange` simplifies this logic: normally, deleting characters at
                // the start would shift the end index. However, because `trackedRange`
                // automatically tracks text updates and adjusts its offsets
                // dynamically, we can safely delete the target range without having to
                // calculate the offset manually.
                delete(trackedRange.textRange.start, trackedRange.textRange.start + 2)
                delete(trackedRange.textRange.end - 2, trackedRange.textRange.end)
            }
    }
}

Column {
    Text("Type **text** below to automatically bold it.")

    BasicTextField(
        state = state,
        textStyle = LocalTextStyle.current,
        inputTransformation = inputTransformation,
    )
}
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// This sample demonstrates a realistic rich-text editor scenario using the `TrackedRange` and
// `TextFieldTextStyles` APIs. It implements a "Toggle Bold" formatting function on the current
// selection.

// For simplicity, this sample keeps bold styles non-overlapping and contiguous, assuming they
// are
// applied exclusively through this method.
val state = rememberTextFieldState("Hello World")

// This derived state calculates whether the current selection is completely covered by
// bold text styles. This ensures the "Bold" toggle button accurately reflects the
// state of the selected text.
val isSelection100PercentBold by remember {
    derivedStateOf {
        val selection = state.selection
        if (selection.collapsed) {
            false
        } else {
            val spanStyles = state.textStyles.getSpanStyles(selection)
            var boldCoverage = 0
            for (style in spanStyles) {
                if (style.item.fontWeight == FontWeight.Bold) {
                    val overlapStart = maxOf(style.start, selection.min)
                    val overlapEnd = minOf(style.end, selection.max)
                    if (overlapEnd > overlapStart) {
                        boldCoverage += (overlapEnd - overlapStart)
                    }
                }
            }
            boldCoverage == selection.length
        }
    }
}

fun TextFieldBuffer.unBoldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // We modify or remove existing styles to exclude the selected range
    for (style in intersectingStyles) {
        val range = style.textRange
        if (range.start >= selection.min && range.end <= selection.max) {
            // The style is fully inside the selection. Remove it.
            removeStyle(style)
        } else if (range.start < selection.min && range.end > selection.max) {
            // The style completely covers the selection. We need to split it.
            val oldEnd = range.end
            // Truncate the start part
            style.textRange = TextRange(range.start, selection.min)
            // Add a new style for the end part
            addStyle(
                SpanStyle(fontWeight = FontWeight.Bold),
                TextRange(selection.max, oldEnd),
                ExpandPolicy.AtEnd,
            )
        } else if (range.start < selection.min) {
            // The style overlaps with the start of the selection. Truncate it.
            style.textRange = TextRange(range.start, selection.min)
        } else {
            // The style overlaps with the end of the selection. Truncate it.
            style.textRange = TextRange(selection.max, range.end)
        }
    }
}

fun TextFieldBuffer.boldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // To keep bold styles non-overlapping, we merge any intersecting bold
    // styles with the new selection range into a single contiguous bold style.
    var mergedStart = selection.min
    var mergedEnd = selection.max

    for (style in intersectingStyles) {
        mergedStart = minOf(mergedStart, style.textRange.start)
        mergedEnd = maxOf(mergedEnd, style.textRange.end)
        // Remove the fragmented style
        removeStyle(style)
    }

    addStyle(
        SpanStyle(fontWeight = FontWeight.Bold),
        TextRange(mergedStart, mergedEnd),
        ExpandPolicy.AtEnd,
    )
}

Column {
    Button(
        onClick = {
            state.edit {
                val selection = this.selection
                if (selection.collapsed) return@edit
                if (isSelection100PercentBold) {
                    unBoldSelection()
                } else {
                    boldSelection()
                }
            }
        }
    ) {
        Text(
            "B",
            fontWeight = if (isSelection100PercentBold) FontWeight.Bold else FontWeight.Normal,
        )
    }

    BasicTextField(state = state, textStyle = LocalTextStyle.current)
}
```

```kotlin
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.Text
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// Wipe the bold style on a given range using the TrackedRange.textRange API
val state = TextFieldState("Hello World")

state.edit {
    // Assume we want to "wipe" all bold styles from the first 5 characters.
    val rangeToWipe = TextRange(0, 5)

    // Get all span styles that intersect with the wipe range.
    getSpanStyles(rangeToWipe).forEach { trackedRange ->
        if (trackedRange.spanStyle.fontWeight == FontWeight.Bold) {
            val current = trackedRange.textRange

            if (rangeToWipe.start <= current.start && current.end <= rangeToWipe.end) {
                // Case 1: The bold style is entirely within the wipe range, remove it.
                removeStyle(trackedRange)
            } else if (current.start < rangeToWipe.start && rangeToWipe.end < current.end) {
                // Case 2: The wipe range is in the middle: split the style into two parts.
                val oldEnd = current.end
                // Truncate the original style to end at the start of the wipe range.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
                // Add a new bold style starting after the wipe range.
                addStyle(trackedRange.spanStyle, rangeToWipe.end, oldEnd)
            } else if (current.start < rangeToWipe.start) {
                // Case 3: Overlap at the start of wipe: truncate the style's end.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
            } else {
                // Case 4: Overlap at the end of wipe: truncate the style's start.
                trackedRange.textRange = TextRange(rangeToWipe.end, current.end)
            }
        }
    }
}
```

| Parameters |
|---|---|
| `range: https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` | the range to query |

| Returns |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin.collections/-list/index.html<https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle>>` | a list of `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange`s referencing the styles intersecting with the given range, returned in the order they were added to the buffer. |

### placeCursorAfterCharAt

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
fun placeCursorAfterCharAt(index: Int): Unit
```

Places the cursor after the character at the given index.

If `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorAfterCharAt(kotlin.Int)` is inside a surrogate pair or other invalid run, the cursor will be placed at the nearest later index.

To place the cursor at the end of the field, after the last character, pass index `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()` or call `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).placeCursorAtEnd()`.

| Parameters |
|---|---|
| `index: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | Character index to place cursor after, should be in range 0 (inclusive) to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()` (exclusive). |

| See also |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorBeforeCharAt(kotlin.Int)` |   |

### placeCursorBeforeCharAt

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
fun placeCursorBeforeCharAt(index: Int): Unit
```

Places the cursor before the character at the given index.

If `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorBeforeCharAt(kotlin.Int)` is inside a surrogate pair or other invalid run, the cursor will be placed at the nearest earlier index.

To place the cursor at the beginning of the field, pass index 0. To place the cursor at the end of the field, after the last character, pass index `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()` or call `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).placeCursorAtEnd()`.

| Parameters |
|---|---|
| `index: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | Character index to place cursor before, should be in range 0 to `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()`, inclusive. |

| See also |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorAfterCharAt(kotlin.Int)` |   |

### removeStyle

Cmn Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
fun removeStyle(trackedRange: TrackedRange<*>): Boolean
```

Removes the exact style represented by the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#removeStyle(androidx.compose.foundation.text.input.TrackedRange)` from this buffer. This method only removes the specific style range tied to this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` object. Since `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#removeStyle(androidx.compose.foundation.text.input.TrackedRange)` uniquely identifies a style range, it does not remove other styles that happen to occupy the same text range.

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight

// This sample demonstrates how to use the `TrackedRange` API to track and modify text ranges
// dynamically. It implements a basic Markdown-like behavior where text typed inside double
// asterisks (e.g., **bold**) is automatically bolded, and the asterisks are removed.
val state = rememberTextFieldState("")

fun IntRange.toTextRange(): TextRange {
    // Unlike IntRange, TextRange is exclusive at the end.
    return TextRange(first, last + 1)
}

val inputTransformation = remember {
    InputTransformation {
        val text = asCharSequence().toString()
        val matches = "\\*\\*([^*]+)\\*\\*".toRegex().findAll(text).toList()
        matches
            .map { match ->
                val contentRange = match.groups[0]!!.range
                // Apply bold style to the text inside asterisks (including the
                // asterisks for now).
                addStyle(
                    SpanStyle(fontWeight = FontWeight.Bold),
                    contentRange.toTextRange(),
                    ExpandPolicy.InsideOnly,
                )
            }
            .forEach { trackedRange ->
                // Remove the asterisks here.

                // `trackedRange` simplifies this logic: normally, deleting characters at
                // the start would shift the end index. However, because `trackedRange`
                // automatically tracks text updates and adjusts its offsets
                // dynamically, we can safely delete the target range without having to
                // calculate the offset manually.
                delete(trackedRange.textRange.start, trackedRange.textRange.start + 2)
                delete(trackedRange.textRange.end - 2, trackedRange.textRange.end)
            }
    }
}

Column {
    Text("Type **text** below to automatically bold it.")

    BasicTextField(
        state = state,
        textStyle = LocalTextStyle.current,
        inputTransformation = inputTransformation,
    )
}
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// This sample demonstrates a realistic rich-text editor scenario using the `TrackedRange` and
// `TextFieldTextStyles` APIs. It implements a "Toggle Bold" formatting function on the current
// selection.

// For simplicity, this sample keeps bold styles non-overlapping and contiguous, assuming they
// are
// applied exclusively through this method.
val state = rememberTextFieldState("Hello World")

// This derived state calculates whether the current selection is completely covered by
// bold text styles. This ensures the "Bold" toggle button accurately reflects the
// state of the selected text.
val isSelection100PercentBold by remember {
    derivedStateOf {
        val selection = state.selection
        if (selection.collapsed) {
            false
        } else {
            val spanStyles = state.textStyles.getSpanStyles(selection)
            var boldCoverage = 0
            for (style in spanStyles) {
                if (style.item.fontWeight == FontWeight.Bold) {
                    val overlapStart = maxOf(style.start, selection.min)
                    val overlapEnd = minOf(style.end, selection.max)
                    if (overlapEnd > overlapStart) {
                        boldCoverage += (overlapEnd - overlapStart)
                    }
                }
            }
            boldCoverage == selection.length
        }
    }
}

fun TextFieldBuffer.unBoldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // We modify or remove existing styles to exclude the selected range
    for (style in intersectingStyles) {
        val range = style.textRange
        if (range.start >= selection.min && range.end <= selection.max) {
            // The style is fully inside the selection. Remove it.
            removeStyle(style)
        } else if (range.start < selection.min && range.end > selection.max) {
            // The style completely covers the selection. We need to split it.
            val oldEnd = range.end
            // Truncate the start part
            style.textRange = TextRange(range.start, selection.min)
            // Add a new style for the end part
            addStyle(
                SpanStyle(fontWeight = FontWeight.Bold),
                TextRange(selection.max, oldEnd),
                ExpandPolicy.AtEnd,
            )
        } else if (range.start < selection.min) {
            // The style overlaps with the start of the selection. Truncate it.
            style.textRange = TextRange(range.start, selection.min)
        } else {
            // The style overlaps with the end of the selection. Truncate it.
            style.textRange = TextRange(selection.max, range.end)
        }
    }
}

fun TextFieldBuffer.boldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // To keep bold styles non-overlapping, we merge any intersecting bold
    // styles with the new selection range into a single contiguous bold style.
    var mergedStart = selection.min
    var mergedEnd = selection.max

    for (style in intersectingStyles) {
        mergedStart = minOf(mergedStart, style.textRange.start)
        mergedEnd = maxOf(mergedEnd, style.textRange.end)
        // Remove the fragmented style
        removeStyle(style)
    }

    addStyle(
        SpanStyle(fontWeight = FontWeight.Bold),
        TextRange(mergedStart, mergedEnd),
        ExpandPolicy.AtEnd,
    )
}

Column {
    Button(
        onClick = {
            state.edit {
                val selection = this.selection
                if (selection.collapsed) return@edit
                if (isSelection100PercentBold) {
                    unBoldSelection()
                } else {
                    boldSelection()
                }
            }
        }
    ) {
        Text(
            "B",
            fontWeight = if (isSelection100PercentBold) FontWeight.Bold else FontWeight.Normal,
        )
    }

    BasicTextField(state = state, textStyle = LocalTextStyle.current)
}
```

```kotlin
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.Text
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// Wipe the bold style on a given range using the TrackedRange.textRange API
val state = TextFieldState("Hello World")

state.edit {
    // Assume we want to "wipe" all bold styles from the first 5 characters.
    val rangeToWipe = TextRange(0, 5)

    // Get all span styles that intersect with the wipe range.
    getSpanStyles(rangeToWipe).forEach { trackedRange ->
        if (trackedRange.spanStyle.fontWeight == FontWeight.Bold) {
            val current = trackedRange.textRange

            if (rangeToWipe.start <= current.start && current.end <= rangeToWipe.end) {
                // Case 1: The bold style is entirely within the wipe range, remove it.
                removeStyle(trackedRange)
            } else if (current.start < rangeToWipe.start && rangeToWipe.end < current.end) {
                // Case 2: The wipe range is in the middle: split the style into two parts.
                val oldEnd = current.end
                // Truncate the original style to end at the start of the wipe range.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
                // Add a new bold style starting after the wipe range.
                addStyle(trackedRange.spanStyle, rangeToWipe.end, oldEnd)
            } else if (current.start < rangeToWipe.start) {
                // Case 3: Overlap at the start of wipe: truncate the style's end.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
            } else {
                // Case 4: Overlap at the end of wipe: truncate the style's start.
                trackedRange.textRange = TextRange(rangeToWipe.end, current.end)
            }
        }
    }
}
```

| Parameters |
|---|---|
| `trackedRange: https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange<*>` | the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` referencing the specific style range to be removed. If the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#removeStyle(androidx.compose.foundation.text.input.TrackedRange)` was not added to this buffer, or has already been removed, this method will do nothing and return false. |

| Returns |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-boolean/index.html` | true if the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#removeStyle(androidx.compose.foundation.text.input.TrackedRange)` is found in the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` and successfully removed, false otherwise. |

### replace

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
fun replace(start: Int, end: Int, text: CharSequence): Unit
```

Replaces the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)` (inclusive) and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)` (exclusive) in this value with `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)`, and records the change in `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#changes()`.

| Parameters |
|---|---|
| `start: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | The character offset of the first character to replace. |
| `end: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | The character offset of the first character after the text to replace. |
| `text: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-char-sequence/index.html` | The text to replace the range `[start, end)` with. |

| See also |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#append(kotlin.CharSequence)` |   |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` |   |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` |   |

### revertAllChanges

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
fun revertAllChanges(): Unit
```

Revert all changes made to this value since it was created.

After calling this method, this object will be in the same state it was when it was initially created, and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#changes()` will be empty.

### toString

Cmn

```
open fun toString(): String
```

## Public properties

### changes

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
val changes: TextFieldBuffer.ChangeList
```

The `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.ChangeList` represents the changes made to this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` and is inherently mutable. This means that the returned `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer.ChangeList` always reflects the complete list of changes made to this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` at any given time, even those made after reading this property.

```kotlin
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.forEachChange
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.substring

// Print a log message every time the text is changed.
BasicTextField(
    state = rememberTextFieldState(),
    inputTransformation = {
        changes.forEachChange { sourceRange, replacedLength ->
            val newString = asCharSequence().substring(sourceRange)
            println("""$replacedLength characters were replaced with "$newString"""")
        }
    },
)
```

```kotlin
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.forEachChange
import androidx.compose.foundation.text.input.forEachChangeReversed
import androidx.compose.foundation.text.input.insert
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Text
import androidx.compose.runtime.remember

// Make a text field behave in "insert mode" -- inserted text overwrites the text ahead of it
// instead of being inserted.
BasicTextField(
    state = rememberTextFieldState(),
    inputTransformation = {
        changes.forEachChangeReversed { range, originalRange ->
            if (!range.collapsed && originalRange.collapsed) {
                // New text was inserted, delete the text ahead of it.
                delete(
                    range.end.coerceAtMost(length),
                    (range.end + range.length).coerceAtMost(length),
                )
            }
        }
    },
)
```

### TrackedRange.expandPolicy

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+symbol:expandPolicy)

```
var TrackedRange<*>.expandPolicy: ExpandPolicy
```

The `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy` defining how the style range expands when text is inserted at its boundaries.

This property is only accessible within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` scope where the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` was created.

Modifying the text can potentially invalidate a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` if its length collapses to zero. If this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` is no longer valid, this property will return `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/ExpandPolicy#InsideOnly()`, and setting this property will do nothing.

Setting this property will update the expand policy in-place, preserving its original applying order relative to other styles in the buffer.

```kotlin
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.material3.Text
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange

// This sample demonstrates the use of [TrackedRange.valid] and [TrackedRange.expandPolicy]. It
// shows how mutating the text can cause a TrackedRange to become invalid, and how to
// dynamically
// update the behavior of a range.
val state = TextFieldState("Hello World")

state.edit {
    // Query the existing styles on the text
    val existingStyles = getSpanStyles(TextRange(0, length))

    existingStyles.forEach { trackedRange ->
        // Read and update the expand policy of a style
        if (trackedRange.expandPolicy == ExpandPolicy.InsideOnly) {
            trackedRange.expandPolicy = ExpandPolicy.AtEnd
        }
    }

    // Do some edits that might delete the styled text
    delete(0, 5)

    // After the edits, we can check if the previously queried ranges are still valid
    existingStyles.forEach { trackedRange ->
        // The style's range might have collapsed to zero length, making it no longer valid.
        // It is recommended to check validity before accessing properties like textRange.
        if (trackedRange.isValid) {
            // Style is still valid, it's up-to-date range can be accessed via
            // trackedRange.textRange
        } else {
            // Style was completely deleted.
        }
    }
}
```

### hasSelection

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
val hasSelection: Boolean
```

True if the selection range has non-zero length. If this is false, then the selection represents the cursor.

| See also |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#selection()` |   |

### TrackedRange.isValid

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+symbol:isValid) Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
val TrackedRange<*>.isValid: Boolean
```

Whether this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` is still valid in the buffer.

A `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` is removed from this buffer when `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#removeStyle(androidx.compose.foundation.text.input.TrackedRange)` is called, or when its length collapses to zero due to text edits. Once it's no longer valid, accessing its other properties will return default values, and modifying them will have no effect.

This property is only accessible within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` scope where the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` was created.

```kotlin
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.material3.Text
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange

// This sample demonstrates the use of [TrackedRange.valid] and [TrackedRange.expandPolicy]. It
// shows how mutating the text can cause a TrackedRange to become invalid, and how to
// dynamically
// update the behavior of a range.
val state = TextFieldState("Hello World")

state.edit {
    // Query the existing styles on the text
    val existingStyles = getSpanStyles(TextRange(0, length))

    existingStyles.forEach { trackedRange ->
        // Read and update the expand policy of a style
        if (trackedRange.expandPolicy == ExpandPolicy.InsideOnly) {
            trackedRange.expandPolicy = ExpandPolicy.AtEnd
        }
    }

    // Do some edits that might delete the styled text
    delete(0, 5)

    // After the edits, we can check if the previously queried ranges are still valid
    existingStyles.forEach { trackedRange ->
        // The style's range might have collapsed to zero length, making it no longer valid.
        // It is recommended to check validity before accessing properties like textRange.
        if (trackedRange.isValid) {
            // Style is still valid, it's up-to-date range can be accessed via
            // trackedRange.textRange
        } else {
            // Style was completely deleted.
        }
    }
}
```

### length

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
val length: Int
```

The number of characters in the text field.

### originalSelection

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
val originalSelection: TextRange
```

Original selection before the changes. Calling `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#revertAllChanges()` will set the selection to this value.

### originalText

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
val originalText: CharSequence
```

Original text content of the buffer before any changes were applied. Calling `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#revertAllChanges()` will set the contents of this buffer to this value.

### TrackedRange.paragraphStyle

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+symbol:paragraphStyle) Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
var TrackedRange<ParagraphStyle>.paragraphStyle: ParagraphStyle
```

The `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle` object associated with this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange`.

This property is only accessible within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` block where the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` was created.

Modifying the text can potentially invalidate a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` if its length collapses to zero. If this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` is no longer valid, this property will return an empty `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/ParagraphStyle`, and setting this property will do nothing.

Setting this property will update the style applied to the text in-place, preserving its original applying order relative to other styles in the buffer.

### selection

Cmn Added in [1.7.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.7.0)

```
var selection: TextRange
```

The selected range of characters.

Places the selection around the given range in characters.

If the start or end of TextRange fall inside surrogate pairs or other invalid runs, the values will be adjusted to the nearest earlier and later characters, respectively.

To place the start of the selection at the beginning of the field, set this value to `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange#Zero()`. To place the end of the selection at the end of the field, after the last character, pass `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()`. Passing a zero-length range is the same as calling `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#placeCursorBeforeCharAt(kotlin.Int)`.

### TrackedRange.spanStyle

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+symbol:spanStyle) Added in [1.12.0](https://developer.android.com/jetpack/androidx/releases/compose-foundation#1.12.0)

```
var TrackedRange<SpanStyle>.spanStyle: SpanStyle
```

The `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle` object associated with this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange`.

This property is only accessible within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` block where the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` was created.

Modifying the text can potentially invalidate a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` if its length collapses to zero. If this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` is no longer valid, this property will return an empty `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/SpanStyle`, and setting this property will do nothing.

Setting this property will update the style applied to the text in-place, preserving its original applying order relative to other styles in the buffer.

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight

// This sample demonstrates how to use the `TrackedRange` API to track and modify text ranges
// dynamically. It implements a basic Markdown-like behavior where text typed inside double
// asterisks (e.g., **bold**) is automatically bolded, and the asterisks are removed.
val state = rememberTextFieldState("")

fun IntRange.toTextRange(): TextRange {
    // Unlike IntRange, TextRange is exclusive at the end.
    return TextRange(first, last + 1)
}

val inputTransformation = remember {
    InputTransformation {
        val text = asCharSequence().toString()
        val matches = "\\*\\*([^*]+)\\*\\*".toRegex().findAll(text).toList()
        matches
            .map { match ->
                val contentRange = match.groups[0]!!.range
                // Apply bold style to the text inside asterisks (including the
                // asterisks for now).
                addStyle(
                    SpanStyle(fontWeight = FontWeight.Bold),
                    contentRange.toTextRange(),
                    ExpandPolicy.InsideOnly,
                )
            }
            .forEach { trackedRange ->
                // Remove the asterisks here.

                // `trackedRange` simplifies this logic: normally, deleting characters at
                // the start would shift the end index. However, because `trackedRange`
                // automatically tracks text updates and adjusts its offsets
                // dynamically, we can safely delete the target range without having to
                // calculate the offset manually.
                delete(trackedRange.textRange.start, trackedRange.textRange.start + 2)
                delete(trackedRange.textRange.end - 2, trackedRange.textRange.end)
            }
    }
}

Column {
    Text("Type **text** below to automatically bold it.")

    BasicTextField(
        state = state,
        textStyle = LocalTextStyle.current,
        inputTransformation = inputTransformation,
    )
}
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// This sample demonstrates a realistic rich-text editor scenario using the `TrackedRange` and
// `TextFieldTextStyles` APIs. It implements a "Toggle Bold" formatting function on the current
// selection.

// For simplicity, this sample keeps bold styles non-overlapping and contiguous, assuming they
// are
// applied exclusively through this method.
val state = rememberTextFieldState("Hello World")

// This derived state calculates whether the current selection is completely covered by
// bold text styles. This ensures the "Bold" toggle button accurately reflects the
// state of the selected text.
val isSelection100PercentBold by remember {
    derivedStateOf {
        val selection = state.selection
        if (selection.collapsed) {
            false
        } else {
            val spanStyles = state.textStyles.getSpanStyles(selection)
            var boldCoverage = 0
            for (style in spanStyles) {
                if (style.item.fontWeight == FontWeight.Bold) {
                    val overlapStart = maxOf(style.start, selection.min)
                    val overlapEnd = minOf(style.end, selection.max)
                    if (overlapEnd > overlapStart) {
                        boldCoverage += (overlapEnd - overlapStart)
                    }
                }
            }
            boldCoverage == selection.length
        }
    }
}

fun TextFieldBuffer.unBoldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // We modify or remove existing styles to exclude the selected range
    for (style in intersectingStyles) {
        val range = style.textRange
        if (range.start >= selection.min && range.end <= selection.max) {
            // The style is fully inside the selection. Remove it.
            removeStyle(style)
        } else if (range.start < selection.min && range.end > selection.max) {
            // The style completely covers the selection. We need to split it.
            val oldEnd = range.end
            // Truncate the start part
            style.textRange = TextRange(range.start, selection.min)
            // Add a new style for the end part
            addStyle(
                SpanStyle(fontWeight = FontWeight.Bold),
                TextRange(selection.max, oldEnd),
                ExpandPolicy.AtEnd,
            )
        } else if (range.start < selection.min) {
            // The style overlaps with the start of the selection. Truncate it.
            style.textRange = TextRange(range.start, selection.min)
        } else {
            // The style overlaps with the end of the selection. Truncate it.
            style.textRange = TextRange(selection.max, range.end)
        }
    }
}

fun TextFieldBuffer.boldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // To keep bold styles non-overlapping, we merge any intersecting bold
    // styles with the new selection range into a single contiguous bold style.
    var mergedStart = selection.min
    var mergedEnd = selection.max

    for (style in intersectingStyles) {
        mergedStart = minOf(mergedStart, style.textRange.start)
        mergedEnd = maxOf(mergedEnd, style.textRange.end)
        // Remove the fragmented style
        removeStyle(style)
    }

    addStyle(
        SpanStyle(fontWeight = FontWeight.Bold),
        TextRange(mergedStart, mergedEnd),
        ExpandPolicy.AtEnd,
    )
}

Column {
    Button(
        onClick = {
            state.edit {
                val selection = this.selection
                if (selection.collapsed) return@edit
                if (isSelection100PercentBold) {
                    unBoldSelection()
                } else {
                    boldSelection()
                }
            }
        }
    ) {
        Text(
            "B",
            fontWeight = if (isSelection100PercentBold) FontWeight.Bold else FontWeight.Normal,
        )
    }

    BasicTextField(state = state, textStyle = LocalTextStyle.current)
}
```

```kotlin
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.Text
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// Wipe the bold style on a given range using the TrackedRange.textRange API
val state = TextFieldState("Hello World")

state.edit {
    // Assume we want to "wipe" all bold styles from the first 5 characters.
    val rangeToWipe = TextRange(0, 5)

    // Get all span styles that intersect with the wipe range.
    getSpanStyles(rangeToWipe).forEach { trackedRange ->
        if (trackedRange.spanStyle.fontWeight == FontWeight.Bold) {
            val current = trackedRange.textRange

            if (rangeToWipe.start <= current.start && current.end <= rangeToWipe.end) {
                // Case 1: The bold style is entirely within the wipe range, remove it.
                removeStyle(trackedRange)
            } else if (current.start < rangeToWipe.start && rangeToWipe.end < current.end) {
                // Case 2: The wipe range is in the middle: split the style into two parts.
                val oldEnd = current.end
                // Truncate the original style to end at the start of the wipe range.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
                // Add a new bold style starting after the wipe range.
                addStyle(trackedRange.spanStyle, rangeToWipe.end, oldEnd)
            } else if (current.start < rangeToWipe.start) {
                // Case 3: Overlap at the start of wipe: truncate the style's end.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
            } else {
                // Case 4: Overlap at the end of wipe: truncate the style's start.
                trackedRange.textRange = TextRange(rangeToWipe.end, current.end)
            }
        }
    }
}
```

### TrackedRange.textRange

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+symbol:textRange)

```
var TrackedRange<*>.textRange: TextRange
```

The `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange` of this style. This range will reflect the up-to-date style range as the text is edited.

This property is only accessible within the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer` block where the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` was created. Do not keep a reference to the `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` outside of that block.

Modifying the text can potentially invalidate a `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` if its length collapses to zero. If this `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TrackedRange` is no longer valid, this property will return `https://developer.android.com/reference/kotlin/androidx/compose/ui/text/TextRange#Zero()`, and setting this property will do nothing.

Setting this property will update the range of the style in-place, preserving its original applying order relative to other styles in the buffer.

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.InputTransformation
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.delete
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight

// This sample demonstrates how to use the `TrackedRange` API to track and modify text ranges
// dynamically. It implements a basic Markdown-like behavior where text typed inside double
// asterisks (e.g., **bold**) is automatically bolded, and the asterisks are removed.
val state = rememberTextFieldState("")

fun IntRange.toTextRange(): TextRange {
    // Unlike IntRange, TextRange is exclusive at the end.
    return TextRange(first, last + 1)
}

val inputTransformation = remember {
    InputTransformation {
        val text = asCharSequence().toString()
        val matches = "\\*\\*([^*]+)\\*\\*".toRegex().findAll(text).toList()
        matches
            .map { match ->
                val contentRange = match.groups[0]!!.range
                // Apply bold style to the text inside asterisks (including the
                // asterisks for now).
                addStyle(
                    SpanStyle(fontWeight = FontWeight.Bold),
                    contentRange.toTextRange(),
                    ExpandPolicy.InsideOnly,
                )
            }
            .forEach { trackedRange ->
                // Remove the asterisks here.

                // `trackedRange` simplifies this logic: normally, deleting characters at
                // the start would shift the end index. However, because `trackedRange`
                // automatically tracks text updates and adjusts its offsets
                // dynamically, we can safely delete the target range without having to
                // calculate the offset manually.
                delete(trackedRange.textRange.start, trackedRange.textRange.start + 2)
                delete(trackedRange.textRange.end - 2, trackedRange.textRange.end)
            }
    }
}

Column {
    Text("Type **text** below to automatically bold it.")

    BasicTextField(
        state = state,
        textStyle = LocalTextStyle.current,
        inputTransformation = inputTransformation,
    )
}
```

```kotlin
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.foundation.text.input.ExpandPolicy
import androidx.compose.foundation.text.input.TextFieldBuffer
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.foundation.text.input.rememberTextFieldState
import androidx.compose.material3.Button
import androidx.compose.material3.LocalTextStyle
import androidx.compose.material3.Text
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// This sample demonstrates a realistic rich-text editor scenario using the `TrackedRange` and
// `TextFieldTextStyles` APIs. It implements a "Toggle Bold" formatting function on the current
// selection.

// For simplicity, this sample keeps bold styles non-overlapping and contiguous, assuming they
// are
// applied exclusively through this method.
val state = rememberTextFieldState("Hello World")

// This derived state calculates whether the current selection is completely covered by
// bold text styles. This ensures the "Bold" toggle button accurately reflects the
// state of the selected text.
val isSelection100PercentBold by remember {
    derivedStateOf {
        val selection = state.selection
        if (selection.collapsed) {
            false
        } else {
            val spanStyles = state.textStyles.getSpanStyles(selection)
            var boldCoverage = 0
            for (style in spanStyles) {
                if (style.item.fontWeight == FontWeight.Bold) {
                    val overlapStart = maxOf(style.start, selection.min)
                    val overlapEnd = minOf(style.end, selection.max)
                    if (overlapEnd > overlapStart) {
                        boldCoverage += (overlapEnd - overlapStart)
                    }
                }
            }
            boldCoverage == selection.length
        }
    }
}

fun TextFieldBuffer.unBoldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // We modify or remove existing styles to exclude the selected range
    for (style in intersectingStyles) {
        val range = style.textRange
        if (range.start >= selection.min && range.end <= selection.max) {
            // The style is fully inside the selection. Remove it.
            removeStyle(style)
        } else if (range.start < selection.min && range.end > selection.max) {
            // The style completely covers the selection. We need to split it.
            val oldEnd = range.end
            // Truncate the start part
            style.textRange = TextRange(range.start, selection.min)
            // Add a new style for the end part
            addStyle(
                SpanStyle(fontWeight = FontWeight.Bold),
                TextRange(selection.max, oldEnd),
                ExpandPolicy.AtEnd,
            )
        } else if (range.start < selection.min) {
            // The style overlaps with the start of the selection. Truncate it.
            style.textRange = TextRange(range.start, selection.min)
        } else {
            // The style overlaps with the end of the selection. Truncate it.
            style.textRange = TextRange(selection.max, range.end)
        }
    }
}

fun TextFieldBuffer.boldSelection() {
    // Query existing bold styles in the selection
    val intersectingStyles =
        getSpanStyles(selection).filter { it.spanStyle.fontWeight == FontWeight.Bold }
    // To keep bold styles non-overlapping, we merge any intersecting bold
    // styles with the new selection range into a single contiguous bold style.
    var mergedStart = selection.min
    var mergedEnd = selection.max

    for (style in intersectingStyles) {
        mergedStart = minOf(mergedStart, style.textRange.start)
        mergedEnd = maxOf(mergedEnd, style.textRange.end)
        // Remove the fragmented style
        removeStyle(style)
    }

    addStyle(
        SpanStyle(fontWeight = FontWeight.Bold),
        TextRange(mergedStart, mergedEnd),
        ExpandPolicy.AtEnd,
    )
}

Column {
    Button(
        onClick = {
            state.edit {
                val selection = this.selection
                if (selection.collapsed) return@edit
                if (isSelection100PercentBold) {
                    unBoldSelection()
                } else {
                    boldSelection()
                }
            }
        }
    ) {
        Text(
            "B",
            fontWeight = if (isSelection100PercentBold) FontWeight.Bold else FontWeight.Normal,
        )
    }

    BasicTextField(state = state, textStyle = LocalTextStyle.current)
}
```

```kotlin
import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.material3.Text
import androidx.compose.ui.text.SpanStyle
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp

// Wipe the bold style on a given range using the TrackedRange.textRange API
val state = TextFieldState("Hello World")

state.edit {
    // Assume we want to "wipe" all bold styles from the first 5 characters.
    val rangeToWipe = TextRange(0, 5)

    // Get all span styles that intersect with the wipe range.
    getSpanStyles(rangeToWipe).forEach { trackedRange ->
        if (trackedRange.spanStyle.fontWeight == FontWeight.Bold) {
            val current = trackedRange.textRange

            if (rangeToWipe.start <= current.start && current.end <= rangeToWipe.end) {
                // Case 1: The bold style is entirely within the wipe range, remove it.
                removeStyle(trackedRange)
            } else if (current.start < rangeToWipe.start && rangeToWipe.end < current.end) {
                // Case 2: The wipe range is in the middle: split the style into two parts.
                val oldEnd = current.end
                // Truncate the original style to end at the start of the wipe range.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
                // Add a new bold style starting after the wipe range.
                addStyle(trackedRange.spanStyle, rangeToWipe.end, oldEnd)
            } else if (current.start < rangeToWipe.start) {
                // Case 3: Overlap at the start of wipe: truncate the style's end.
                trackedRange.textRange = TextRange(current.start, rangeToWipe.start)
            } else {
                // Case 4: Overlap at the end of wipe: truncate the style's start.
                trackedRange.textRange = TextRange(rangeToWipe.end, current.end)
            }
        }
    }
}
```

| Throws |
|---|---|
| `https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-illegal-argument-exception/index.html` | if the new range is collapsed, reversed or out of range. |

## Extension functions

### TextFieldBuffer.delete

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+function:delete)

```
fun TextFieldBuffer.delete(start: Int, end: Int): Unit
```

Delete the text between `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` (inclusive) and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` (exclusive). Pass 0 as `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` and `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()` as `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` to delete everything in this buffer.

| Parameters |
|---|---|
| `start: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | The character offset of the first character to delete. |
| `end: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | The character offset of the first character after the deleted range. |

| See also |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)` |   |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#append(kotlin.CharSequence)` |   |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` |   |

### TextFieldBuffer.insert

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+function:insert)

```
fun TextFieldBuffer.insert(index: Int, text: String): Unit
```

Insert `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` at the given `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` in this value. Pass 0 to insert `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` at the beginning of this buffer, and pass `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#length()` to insert `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)` at the end of this buffer.

This is equivalent to calling `replace(index, index, text)`.

| Parameters |
|---|---|
| `index: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-int/index.html` | The character offset at which to insert `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).insert(kotlin.Int,kotlin.String)`. |
| `text: https://kotlinlang.org/api/core/kotlin-stdlib/kotlin/-string/index.html` | The text to insert. |

| See also |
|---|---|
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#replace(kotlin.Int,kotlin.Int,kotlin.CharSequence)` |   |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/TextFieldBuffer#append(kotlin.CharSequence)` |   |
| `https://developer.android.com/reference/kotlin/androidx/compose/foundation/text/input/package-summary#(androidx.compose.foundation.text.input.TextFieldBuffer).delete(kotlin.Int,kotlin.Int)` |   |

### TextFieldBuffer.placeCursorAtEnd

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+function:placeCursorAtEnd)

```
fun TextFieldBuffer.placeCursorAtEnd(): Unit
```

Places the cursor at the end of the text.

### TextFieldBuffer.selectAll

Cmn Artifact: [androidx.compose.foundation:foundation](https://developer.android.com/jetpack/androidx/releases/compose-foundation) [View Source](https://cs.android.com/search?q=file:androidx/compose/foundation/text/input/TextFieldBuffer.kt+function:selectAll)

```
fun TextFieldBuffer.selectAll(): Unit
```

Places the selection around all the text.