1 /*
2  * Copyright 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 @file:OptIn(ExperimentalFoundationApi::class)
18 
19 package androidx.compose.foundation.text.input
20 
21 import androidx.annotation.VisibleForTesting
22 import androidx.compose.foundation.ExperimentalFoundationApi
23 import androidx.compose.foundation.internal.checkPrecondition
24 import androidx.compose.foundation.text.input.internal.undo.TextFieldEditUndoBehavior
25 import androidx.compose.runtime.Composable
26 import androidx.compose.runtime.Stable
27 import androidx.compose.runtime.annotation.RememberInComposition
28 import androidx.compose.runtime.collection.MutableVector
29 import androidx.compose.runtime.collection.mutableVectorOf
30 import androidx.compose.runtime.getValue
31 import androidx.compose.runtime.mutableStateOf
32 import androidx.compose.runtime.saveable.SaverScope
33 import androidx.compose.runtime.saveable.rememberSaveable
34 import androidx.compose.runtime.setValue
35 import androidx.compose.runtime.snapshotFlow
36 import androidx.compose.runtime.snapshots.Snapshot
37 import androidx.compose.ui.text.AnnotatedString
38 import androidx.compose.ui.text.SpanStyle
39 import androidx.compose.ui.text.TextRange
40 import androidx.compose.ui.text.coerceIn
41 import androidx.compose.ui.text.style.TextDecoration
42 
43 /**
44  * The editable text state of a text field, including both the [text] itself and position of the
45  * cursor or selection.
46  *
47  * To change the text field contents programmatically, call [edit], [setTextAndSelectAll],
48  * [setTextAndPlaceCursorAtEnd], or [clearText]. Individual parts of the state like [text],
49  * [selection], or [composition] can be read from any snapshot restart scope like Composable
50  * functions. To observe these members from outside a restart scope, use `snapshotFlow {
51  * textFieldState.text }` or `snapshotFlow { textFieldState.selection }`.
52  *
53  * When instantiating this class from a composable, use [rememberTextFieldState] to automatically
54  * save and restore the field state. For more advanced use cases, pass [TextFieldState.Saver] to
55  * [rememberSaveable].
56  *
57  * @sample androidx.compose.foundation.samples.BasicTextFieldStateCompleteSample
58  */
59 @Stable
60 class TextFieldState
61 internal constructor(
62     initialText: String,
63     initialSelection: TextRange,
64     initialTextUndoManager: TextUndoManager
65 ) {
66 
67     @RememberInComposition
68     constructor(
69         initialText: String = "",
70         initialSelection: TextRange = TextRange(initialText.length)
71     ) : this(initialText, initialSelection, TextUndoManager())
72 
73     /** Manages the history of edit operations that happen in this [TextFieldState]. */
74     internal val textUndoManager: TextUndoManager = initialTextUndoManager
75 
76     /**
77      * The buffer used for applying editor commands from IME. All edits coming from gestures or IME
78      * commands must be reflected on this buffer eventually.
79      */
80     @VisibleForTesting
81     internal var mainBuffer: TextFieldBuffer =
82         TextFieldBuffer(
83             initialValue =
84                 TextFieldCharSequence(
85                     text = initialText,
86                     selection = initialSelection.coerceIn(0, initialText.length)
87                 )
88         )
89 
90     /**
91      * [TextFieldState] does not synchronize calls to [edit] but requires main thread access. It
92      * also has no way to disallow reentrant behavior (nested calls to [edit]) through the API.
93      * Instead we keep track of whether an edit session is currently running. If [edit] is called
94      * concurrently or reentered, it should throw an exception. The only exception is if
95      * [TextFieldState] is being modified in two different snapshots. Hence, this value is backed by
96      * a snapshot state.
97      */
98     private var isEditing: Boolean by mutableStateOf(false)
99 
100     /**
101      * The current text, selection, and composing region. This value will automatically update when
102      * the user enters text or otherwise changes the text field contents. To change it
103      * programmatically, call [edit].
104      *
105      * This is backed by snapshot state, so reading this property in a restartable function (e.g. a
106      * composable function) will cause the function to restart when the text field's value changes.
107      *
108      * @sample androidx.compose.foundation.samples.BasicTextFieldTextDerivedStateSample
109      * @see edit
110      */
111     internal var value: TextFieldCharSequence by
112         mutableStateOf(TextFieldCharSequence(initialText, initialSelection))
113         /** Do not set directly. Always go through [updateValueAndNotifyListeners]. */
114         private set
115 
116     /**
117      * The current text content. This value will automatically update when the user enters text or
118      * otherwise changes the text field contents. To change it programmatically, call [edit].
119      *
120      * To observe changes to this property outside a restartable function, use `snapshotFlow { text
121      * }`.
122      *
123      * @sample androidx.compose.foundation.samples.BasicTextFieldTextValuesSample
124      * @see edit
125      * @see snapshotFlow
126      */
127     val text: CharSequence
128         get() = value.text
129 
130     /**
131      * The current selection range. If the selection is collapsed, it represents cursor location.
132      * This value will automatically update when the user enters text or otherwise changes the text
133      * field selection range. To change it programmatically, call [edit].
134      *
135      * To observe changes to this property outside a restartable function, use `snapshotFlow {
136      * selection }`.
137      *
138      * @see edit
139      * @see snapshotFlow
140      * @see TextFieldCharSequence.selection
141      */
142     val selection: TextRange
143         get() = value.selection
144 
145     /**
146      * The current composing range dictated by the IME. If null, there is no composing region.
147      *
148      * To observe changes to this property outside a restartable function, use `snapshotFlow {
149      * composition }`.
150      *
151      * @see edit
152      * @see snapshotFlow
153      * @see TextFieldCharSequence.composition
154      */
155     val composition: TextRange?
156         get() = value.composition
157 
158     /**
159      * Runs [block] with a mutable version of the current state. The block can make changes to the
160      * text and cursor/selection. See the documentation on [TextFieldBuffer] for a more detailed
161      * description of the available operations.
162      *
163      * Make sure that you do not make concurrent calls to this function or call it again inside
164      * [block]'s scope. Doing either of these actions will result in triggering an
165      * [IllegalStateException].
166      *
167      * @sample androidx.compose.foundation.samples.BasicTextFieldStateEditSample
168      * @see setTextAndPlaceCursorAtEnd
169      * @see setTextAndSelectAll
170      */
editnull171     inline fun edit(block: TextFieldBuffer.() -> Unit) {
172         val mutableValue = startEdit()
173         try {
174             mutableValue.block()
175             commitEdit(mutableValue)
176         } finally {
177             finishEditing()
178         }
179     }
180 
toStringnull181     override fun toString(): String =
182         Snapshot.withoutReadObservation { "TextFieldState(selection=$selection, text=\"$text\")" }
183 
184     /**
185      * Undo history controller for this TextFieldState.
186      *
187      * @sample androidx.compose.foundation.samples.BasicTextFieldUndoSample
188      */
189     // TextField does not implement UndoState because Undo related APIs should be able to remain
190     // separately experimental than TextFieldState
191     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
192     @ExperimentalFoundationApi
193     @get:ExperimentalFoundationApi
194     val undoState: UndoState = UndoState(this)
195 
196     @Suppress("ShowingMemberInHiddenClass")
197     @PublishedApi
startEditnull198     internal fun startEdit(): TextFieldBuffer {
199         val isEditingFreeze = Snapshot.withoutReadObservation { isEditing }
200         checkPrecondition(!isEditingFreeze) {
201             "TextFieldState does not support concurrent or nested editing."
202         }
203         isEditing = true
204         return TextFieldBuffer(value)
205     }
206 
207     /**
208      * If the text or selection in [newValue] was actually modified, updates this state's internal
209      * values. If [newValue] was not modified at all, the state is not updated, and this will not
210      * invalidate anyone who is observing this state.
211      *
212      * @param newValue [TextFieldBuffer] that contains the latest updates
213      */
214     @Suppress("ShowingMemberInHiddenClass")
215     @PublishedApi
commitEditnull216     internal fun commitEdit(newValue: TextFieldBuffer) {
217         val textChanged = newValue.changes.changeCount > 0
218         val selectionChanged = newValue.selection != mainBuffer.selection
219         if (textChanged) {
220             // clear the undo history after a programmatic edit if the text content has changed
221             textUndoManager.clearHistory()
222         }
223         syncMainBufferToTemporaryBuffer(
224             temporaryBuffer = newValue,
225             textChanged = textChanged,
226             selectionChanged = selectionChanged
227         )
228     }
229 
230     @Suppress("ShowingMemberInHiddenClass")
231     @PublishedApi
finishEditingnull232     internal fun finishEditing() {
233         isEditing = false
234     }
235 
236     /**
237      * An edit block that updates [TextFieldState] on behalf of user actions such as gestures, IME
238      * commands, hardware keyboard events, clipboard actions, and more. These modifications must
239      * also run through the given [inputTransformation] since they are user actions.
240      *
241      * Be careful that this method is not snapshot aware. It is only safe to call this from main
242      * thread, or global snapshot. Also, this function is defined as inline for performance gains,
243      * and it's not actually safe to early return from [block].
244      *
245      * Also all user edits should be recorded by [textUndoManager] since reverting to a previous
246      * state requires all edit operations to be executed in reverse. However, some commands like
247      * cut, and paste should be atomic operations that do not merge with previous or next operations
248      * in the Undo stack. This can be controlled by [undoBehavior].
249      *
250      * @param inputTransformation [InputTransformation] to run after [block] is applied
251      * @param restartImeIfContentChanges Whether IME should be restarted if the proposed changes end
252      *   up editing the text content. Only pass false to this argument if the source of the changes
253      *   is IME itself.
254      * @param block The function that updates the current buffer.
255      */
editAsUsernull256     internal inline fun editAsUser(
257         inputTransformation: InputTransformation?,
258         restartImeIfContentChanges: Boolean = true,
259         undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
260         block: TextFieldBuffer.() -> Unit
261     ) {
262         mainBuffer.changeTracker.clearChanges()
263         mainBuffer.block()
264 
265         commitEditAsUser(
266             inputTransformation = inputTransformation,
267             restartImeIfContentChanges = restartImeIfContentChanges,
268             undoBehavior = undoBehavior
269         )
270     }
271 
272     /**
273      * Edits the contents of this [TextFieldState] without going through an [InputTransformation],
274      * or recording the changes to the [textUndoManager]. IME would still be notified of any changes
275      * committed by [block].
276      *
277      * This method of editing is not recommended for majority of use cases. It is originally added
278      * to support applying of undo/redo actions without clearing the history. Also, it doesn't
279      * allocate an additional buffer like [edit] method because changes are ignored and it's not a
280      * public API.
281      */
editWithNoSideEffectsnull282     internal inline fun editWithNoSideEffects(block: TextFieldBuffer.() -> Unit) {
283         mainBuffer.changeTracker.clearChanges()
284         mainBuffer.block()
285 
286         val afterEditValue = mainBuffer.toTextFieldCharSequence()
287 
288         updateValueAndNotifyListeners(
289             oldValue = value,
290             newValue = afterEditValue,
291             restartImeIfContentChanges = true
292         )
293     }
294 
295     // Do not inline this function into editAsUser. Inline functions should be kept short.
commitEditAsUsernull296     private fun commitEditAsUser(
297         inputTransformation: InputTransformation?,
298         restartImeIfContentChanges: Boolean = true,
299         undoBehavior: TextFieldEditUndoBehavior = TextFieldEditUndoBehavior.MergeIfPossible,
300     ) {
301         val beforeEditValue = value
302 
303         // first immediately check whether there's any actual change of content or selection.
304         // We only look at the operation count for content change because inputTransformation
305         // should still run even if text doesn't change as a result of an input.
306         // If there is no change at all, or only composition or highlight has changed, we can end
307         // early.
308         if (
309             mainBuffer.changeTracker.changeCount == 0 &&
310                 beforeEditValue.selection == mainBuffer.selection
311         ) {
312             if (
313                 beforeEditValue.composition != mainBuffer.composition ||
314                     beforeEditValue.highlight != mainBuffer.highlight ||
315                     beforeEditValue.composingAnnotations != mainBuffer.composingAnnotations
316             ) {
317                 // edit operation caused no change to text content or selection
318                 // No need to run an existing InputTransformation, or record an undo. Only update
319                 // the IME that composition has been accepted.
320                 updateValueAndNotifyListeners(
321                     oldValue = value,
322                     newValue =
323                         TextFieldCharSequence(
324                             text = mainBuffer.toString(),
325                             selection = mainBuffer.selection,
326                             composition = mainBuffer.composition,
327                             highlight = mainBuffer.highlight,
328                             composingAnnotations =
329                                 finalizeComposingAnnotations(
330                                     composition = mainBuffer.composition,
331                                     annotationList = mainBuffer.composingAnnotations
332                                 )
333                         ),
334                     restartImeIfContentChanges = restartImeIfContentChanges
335                 )
336             }
337             return
338         }
339 
340         // Eventually we may need to run a string equality check between old value and the new value
341         // This is an O(n) operation, meaning that it gets as expensive as the text length.
342         // Therefore we create this flag to remind ourselves whether the original changes may have
343         // caused a content difference. This value being false is a strong indicator that the
344         // content definitely hasn't changed. However this being true only introduces a possibility.
345         // The content change may have been an exact string replacement like "ab" => "ab".
346         val contentMayHaveChanged = mainBuffer.changeTracker.changeCount != 0
347 
348         // There's a meaningful change to the buffer, either content or selection. We need to run
349         // the full logic including InputTransformation. But F=first take a _snapshot_ of current
350         // state of the mainBuffer after changes are applied.
351         val afterEditValue =
352             TextFieldCharSequence(
353                 text = mainBuffer.toString(),
354                 selection = mainBuffer.selection,
355                 composition = mainBuffer.composition,
356                 highlight = mainBuffer.highlight,
357                 composingAnnotations =
358                     finalizeComposingAnnotations(
359                         composition = mainBuffer.composition,
360                         annotationList = mainBuffer.composingAnnotations
361                     )
362             )
363 
364         // if there's no filter; just record the undo, update the snapshot value, end.
365         if (inputTransformation == null) {
366             updateValueAndNotifyListeners(
367                 oldValue = beforeEditValue,
368                 newValue = afterEditValue,
369                 // updateValueAndNotifyListeners use restartImeIfContentChanges flag to possibly
370                 // skip doing string equality check. Here we add our own flag to indicate the
371                 // possibility of content changing. Since false value is a string indicator,
372                 // this added logic works.
373                 restartImeIfContentChanges = contentMayHaveChanged && restartImeIfContentChanges
374             )
375             recordEditForUndo(
376                 previousValue = beforeEditValue,
377                 postValue = afterEditValue,
378                 changes = mainBuffer.changeTracker,
379                 undoBehavior = undoBehavior
380             )
381             return
382         }
383 
384         // Prepare a TextFieldBuffer to run InputTransformation. TextFieldBuffer should be
385         // initialized with the edits that are already applied on mainBuffer, hence the difference
386         // between originalValue and initialValue.
387         val textFieldBuffer =
388             TextFieldBuffer(
389                 originalValue = beforeEditValue,
390                 initialValue = afterEditValue,
391                 initialChanges = mainBuffer.changeTracker
392             )
393 
394         // apply the inputTransformation.
395         with(inputTransformation) { textFieldBuffer.transformInput() }
396 
397         val textChangedByFilter = !textFieldBuffer.asCharSequence().contentEquals(afterEditValue)
398         val selectionChangedByFilter = textFieldBuffer.selection != afterEditValue.selection
399         if (textChangedByFilter || selectionChangedByFilter) {
400             syncMainBufferToTemporaryBuffer(
401                 temporaryBuffer = textFieldBuffer,
402                 textChanged = textChangedByFilter,
403                 selectionChanged = selectionChangedByFilter
404             )
405         } else {
406             updateValueAndNotifyListeners(
407                 oldValue = beforeEditValue,
408                 // If neither the text nor the selection changed by the filter, we want to preserve
409                 // the composition. Otherwise, the IME will reset it anyway.
410                 newValue =
411                     textFieldBuffer.toTextFieldCharSequence(
412                         composition = afterEditValue.composition
413                     ),
414                 restartImeIfContentChanges = restartImeIfContentChanges
415             )
416         }
417         // textFieldBuffer contains all the changes from both the user and the filter.
418         recordEditForUndo(
419             previousValue = beforeEditValue,
420             postValue = value,
421             changes = textFieldBuffer.changes,
422             undoBehavior = undoBehavior
423         )
424     }
425 
426     /**
427      * There are 3 types of edits that are defined on [TextFieldState];
428      * 1. Programmatic changes that are created by [edit] function,
429      * 2. User edits coming from the system that are initiated through IME, semantics, or gestures
430      * 3. Applying Undo/Redo actions that should not trigger side effects.
431      *
432      * Eventually all changes, no matter the source, should be committed to [value]. Also, they have
433      * to trigger the content change listeners.
434      *
435      * Finally notifies the listeners in [notifyImeListeners] that the contents of this
436      * [TextFieldState] has changed.
437      */
updateValueAndNotifyListenersnull438     private fun updateValueAndNotifyListeners(
439         oldValue: TextFieldCharSequence,
440         newValue: TextFieldCharSequence,
441         restartImeIfContentChanges: Boolean
442     ) {
443         // value must be set before notifyImeListeners are called. Even though we are sending the
444         // previous and current values, a system callback may request the latest state e.g. IME
445         // restartInput call is handled before notifyImeListeners return.
446         value = newValue
447         finishEditing()
448 
449         notifyImeListeners.forEach {
450             it.onChange(
451                 oldValue = oldValue,
452                 newValue = newValue,
453                 restartIme =
454                     restartImeIfContentChanges &&
455                         !oldValue.contentEquals(newValue)
456                         // No need to restart the IME if there wasn't a composing region. This is
457                         // useful to not unnecessarily restart digit only, or password fields.
458                         &&
459                         oldValue.composition != null
460             )
461         }
462     }
463 
464     /**
465      * Records the difference between [previousValue] and [postValue], defined by [changes], into
466      * [textUndoManager] according to the strategy defined by [undoBehavior].
467      */
recordEditForUndonull468     private fun recordEditForUndo(
469         previousValue: TextFieldCharSequence,
470         postValue: TextFieldCharSequence,
471         changes: TextFieldBuffer.ChangeList,
472         undoBehavior: TextFieldEditUndoBehavior
473     ) {
474         when (undoBehavior) {
475             TextFieldEditUndoBehavior.ClearHistory -> {
476                 textUndoManager.clearHistory()
477             }
478             TextFieldEditUndoBehavior.MergeIfPossible -> {
479                 textUndoManager.recordChanges(
480                     pre = previousValue,
481                     post = postValue,
482                     changes = changes,
483                     allowMerge = true
484                 )
485             }
486             TextFieldEditUndoBehavior.NeverMerge -> {
487                 textUndoManager.recordChanges(
488                     pre = previousValue,
489                     post = postValue,
490                     changes = changes,
491                     allowMerge = false
492                 )
493             }
494         }
495     }
496 
addNotifyImeListenernull497     internal fun addNotifyImeListener(notifyImeListener: NotifyImeListener) {
498         notifyImeListeners.add(notifyImeListener)
499     }
500 
removeNotifyImeListenernull501     internal fun removeNotifyImeListener(notifyImeListener: NotifyImeListener) {
502         notifyImeListeners.remove(notifyImeListener)
503     }
504 
505     /**
506      * A listener that can be attached to a [TextFieldState] to listen for change events that may
507      * interest IME.
508      *
509      * State in [TextFieldState] can change through various means but categorically there are two
510      * sources; Developer([TextFieldState.edit]) and User([TextFieldState.editAsUser]). Only
511      * non-InputTransformed, IME sourced changes can skip updating the IME. Otherwise, all changes
512      * must be sent to the IME to let it synchronize its state with the [TextFieldState]. Such a
513      * communication channel is established by the text input session registering a
514      * [NotifyImeListener] on a [TextFieldState].
515      */
516     internal fun interface NotifyImeListener {
517 
518         /**
519          * Called when the value in [TextFieldState] changes via any source. The [restartIme] flag
520          * determines whether the ongoing input connection should be restarted. Selection or
521          * Composition range changes never require a restart.
522          *
523          * @param oldValue The previous value of the [TextFieldState] before the latest changes are
524          *   applied with one exception. If an [InputTransformation] is applied on the changes
525          *   coming from the IME, we use the value after user changes are applied but before
526          *   [InputTransformation]. This is essentially the last known state to the IME.
527          * @param newValue Current state of the [TextFieldState]. This is always equal to the
528          *   [TextFieldState.value] at the time of calling this function.
529          * @param restartIme Whether to ignore other parameters and basically restart the input
530          *   session with new configuration.
531          */
onChangenull532         fun onChange(
533             oldValue: TextFieldCharSequence,
534             newValue: TextFieldCharSequence,
535             restartIme: Boolean
536         )
537     }
538 
539     /**
540      * Carries changes made to a [temporaryBuffer] into [mainBuffer], then updates the [value]. This
541      * usually happens when the edit source is something programmatic like [edit] or
542      * [InputTransformation]. Normally IME commands are applied directly on [mainBuffer].
543      *
544      * @param temporaryBuffer Source buffer that will be used to sync the mainBuffer.
545      * @param textChanged Whether the text content inside [temporaryBuffer] is different than
546      *   [mainBuffer]'s text content. Although this value can be calculated by this function, some
547      *   callers already do the comparison before hand, so there's no need to recalculate it.
548      * @param selectionChanged Whether the selection inside [temporaryBuffer] is different than
549      *   [mainBuffer]'s selection.
550      */
551     @VisibleForTesting
552     internal fun syncMainBufferToTemporaryBuffer(
553         temporaryBuffer: TextFieldBuffer,
554         textChanged: Boolean,
555         selectionChanged: Boolean
556     ) {
557         val oldValue = mainBuffer.toTextFieldCharSequence()
558 
559         if (textChanged) {
560             // reset the buffer in its entirety
561             mainBuffer =
562                 TextFieldBuffer(
563                     initialValue =
564                         TextFieldCharSequence(
565                             text = temporaryBuffer.toString(),
566                             selection = temporaryBuffer.selection
567                         ),
568                 )
569         } else if (selectionChanged) {
570             mainBuffer.selection =
571                 TextRange(temporaryBuffer.selection.start, temporaryBuffer.selection.end)
572         }
573 
574         // Composition should be decided by the IME after the content or selection has been
575         // changed programmatically, outside the knowledge of the IME.
576         if (
577             textChanged || selectionChanged || oldValue.composition != temporaryBuffer.composition
578         ) {
579             mainBuffer.commitComposition()
580         }
581 
582         val finalValue = mainBuffer.toTextFieldCharSequence()
583 
584         // We cannot use `value` as the old value here because intermediate IME changes are only
585         // applied on mainBuffer (this only happens if syncMainBufferToTemporaryBuffer is triggered
586         // after an InputTransformation). We must pass in the latest state just before finalValue is
587         // calculated. This is the state IME knows about and is synced with.
588         updateValueAndNotifyListeners(
589             oldValue = oldValue,
590             newValue = finalValue,
591             restartImeIfContentChanges = true
592         )
593     }
594 
595     private val notifyImeListeners = mutableVectorOf<NotifyImeListener>()
596 
597     /**
598      * Saves and restores a [TextFieldState] for [rememberSaveable].
599      *
600      * @see rememberTextFieldState
601      */
602     // Preserve nullability since this is public API.
603     @Suppress("RedundantNullableReturnType")
604     object Saver : androidx.compose.runtime.saveable.Saver<TextFieldState, Any> {
605 
savenull606         override fun SaverScope.save(value: TextFieldState): Any? {
607             return listOf(
608                 value.text.toString(),
609                 value.selection.start,
610                 value.selection.end,
611                 with(TextUndoManager.Companion.Saver) { save(value.textUndoManager) }
612             )
613         }
614 
restorenull615         override fun restore(value: Any): TextFieldState? {
616             val (text, selectionStart, selectionEnd, savedTextUndoManager) = value as List<*>
617             return TextFieldState(
618                 initialText = text as String,
619                 initialSelection =
620                     TextRange(start = selectionStart as Int, end = selectionEnd as Int),
621                 initialTextUndoManager =
622                     with(TextUndoManager.Companion.Saver) { restore(savedTextUndoManager!!) }!!
623             )
624         }
625     }
626 }
627 
628 /**
629  * Create and remember a [TextFieldState]. The state is remembered using [rememberSaveable] and so
630  * will be saved and restored with the composition.
631  *
632  * If you need to store a [TextFieldState] in another object, use the [TextFieldState.Saver] object
633  * to manually save and restore the state.
634  *
635  * @param initialText The initial text state. If a different value is passed in a subsequent
636  *   recomposition, the value of the state will _not_ be updated. To update the state after it's
637  *   initialized, call methods on [TextFieldState].
638  * @param initialSelection The initial selection state. If a different value is passed in a
639  *   subsequent recomposition, the value of the state will _not_ be updated. To update the state
640  *   after it's initialized, call methods on [TextFieldState].
641  */
642 @Composable
rememberTextFieldStatenull643 fun rememberTextFieldState(
644     initialText: String = "",
645     initialSelection: TextRange = TextRange(initialText.length)
646 ): TextFieldState =
647     rememberSaveable(saver = TextFieldState.Saver) { TextFieldState(initialText, initialSelection) }
648 
649 /**
650  * Sets the text in this [TextFieldState] to [text], replacing any text that was previously there,
651  * and places the cursor at the end of the new text.
652  *
653  * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
654  * equivalent to calling:
655  * ```
656  * edit {
657  *   replace(0, length, text)
658  *   placeCursorAtEnd()
659  * }
660  * ```
661  *
662  * @see setTextAndSelectAll
663  * @see clearText
664  * @see TextFieldBuffer.placeCursorAtEnd
665  */
setTextAndPlaceCursorAtEndnull666 fun TextFieldState.setTextAndPlaceCursorAtEnd(text: String) {
667     edit {
668         replace(0, length, text)
669         placeCursorAtEnd()
670     }
671 }
672 
673 /**
674  * Sets the text in this [TextFieldState] to [text], replacing any text that was previously there,
675  * and selects all the text.
676  *
677  * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
678  * equivalent to calling:
679  * ```
680  * edit {
681  *   replace(0, length, text)
682  *   selectAll()
683  * }
684  * ```
685  *
686  * @see setTextAndPlaceCursorAtEnd
687  * @see clearText
688  * @see TextFieldBuffer.selectAll
689  */
setTextAndSelectAllnull690 fun TextFieldState.setTextAndSelectAll(text: String) {
691     edit {
692         replace(0, length, text)
693         selectAll()
694     }
695 }
696 
697 /**
698  * Deletes all the text in the state.
699  *
700  * To perform more complicated edits on the text, call [TextFieldState.edit]. This function is
701  * equivalent to calling:
702  * ```
703  * edit {
704  *   delete(0, length)
705  *   placeCursorAtEnd()
706  * }
707  * ```
708  *
709  * @see setTextAndPlaceCursorAtEnd
710  * @see setTextAndSelectAll
711  */
clearTextnull712 fun TextFieldState.clearText() {
713     edit {
714         delete(0, length)
715         placeCursorAtEnd()
716     }
717 }
718 
719 /**
720  * Final deciding property for which annotations would be rendered for the composing region. If the
721  * IME has not set any composing annotations and the composing region is not collapsed, we need to
722  * add the specific underline styling.
723  */
724 @Suppress("ListIterator")
finalizeComposingAnnotationsnull725 private fun finalizeComposingAnnotations(
726     composition: TextRange?,
727     annotationList: MutableVector<PlacedAnnotation>?
728 ): List<PlacedAnnotation> =
729     when {
730         annotationList != null && annotationList.isNotEmpty() -> {
731             // it is important to freeze the mutable list into an immutable list because
732             // mutable list is sustained inside the EditingBuffer and TextFieldCharSequence
733             // must be read-only.
734             annotationList.asMutableList().toList()
735         }
736         composition != null && !composition.collapsed -> {
737             listOf(
738                 AnnotatedString.Range(
739                     SpanStyle(textDecoration = TextDecoration.Underline),
740                     start = composition.min,
741                     end = composition.max
742                 )
743             )
744         }
745         else -> emptyList()
746     }
747 
748 /**
749  * Creates a temporary, mutable [TextFieldBuffer] representing the current state of this
750  * [TextFieldState].
751  *
752  * Use a [TextFieldBuffer] to:
753  * * Apply transformations for testing purposes
754  * * Preview how the TextField would render with a specific [OutputTransformation]
755  *
756  * This is similar to calling [TextFieldState.edit], but without committing the changes back to the
757  * [TextFieldState].
758  *
759  * **Important:** A [TextFieldBuffer] is intended for short-term use. Let the garbage collecter
760  * dispose of it when you're finished to avoid unnecessary memory usage.
761  *
762  * @sample androidx.compose.foundation.samples.TextFieldStateApplyOutputTransformation
763  */
toTextFieldBuffernull764 fun TextFieldState.toTextFieldBuffer(): TextFieldBuffer {
765     return TextFieldBuffer(value)
766 }
767