Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Text Editing Model Refactor #86736

Merged
merged 84 commits into from
Sep 7, 2021
Merged

Conversation

justinmc
Copy link
Contributor

@justinmc justinmc commented Jul 20, 2021

This PR aims to improve the separation of concerns of our text editing classes.

Design doc: https://docs.google.com/document/d/1lkpg4dfVZif9NJ_dDQ01QkwQC3OqzhUFahhjgv_rcbM/edit#

Related: #80226

Summary

  • TextEditingValue and TextSelection now contain simple methods like extendTo, deleteTo, etc. for manipulating themselves. The logic was abstracted from RenderEditable.
  • TextEditingActionTarget now contains the more specific text manipulation methods, which often call out to TextEditingValue (e.g. extendSelectionRightByLine, deleteToStart, etc.). These were also abstracted from RenderEditable.
  • TextMetrics is a new abstract class, implemented by RenderEditable, that exposes an API for getting text position information only. It's sometimes used by the TextEditingActionTarget methods.
  • Overall result: RenderEditable shrinks by ~2,000 lines of logic unrelated to rendering. All of that logic no longer has access to data from multiple frames, no write access beyond the TextEditingValue.

Reviewing

Sorry this is such a huge diff! It's all moving code around and refactoring it; very little totally new. You can skim most of it because of this. I tried to leave helpful comments where big chunks of code were moved. Let me know if I can explain anything.

My commits are also pretty messy as I explored ideas and changed approaches 😁

@justinmc justinmc self-assigned this Jul 20, 2021
@flutter-dashboard flutter-dashboard bot added the framework flutter/packages/flutter repository. See also f: labels. label Jul 20, 2021
@google-cla google-cla bot added the cla: yes label Jul 20, 2021
@@ -653,110 +643,6 @@ class RawFloatingCursorPoint {
final FloatingCursorDragState state;
}

/// The current text, selection, and composing state for editing a run of text.
@immutable
class TextEditingValue {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TextEditingValue was moved to its own file in order to not pollute this one, now that it's much larger.

@@ -77,47 +77,6 @@ class TextSelectionPoint {
}
}

// Check if the given code unit is a white space or separator
Copy link
Contributor Author

@justinmc justinmc Jul 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This file's diff is really hard to read, but basically what happened was:

  • Lots of logic moved to TextEditingAction, TextEditingValue, and TextSelection.
  • Some alphabetizing/reordering may have happened...
  • TextMetrics was implemented.
  • debugAssertLayoutUpToDate was turned into a function and deduplicated.

@justinmc
Copy link
Contributor Author

@LongCatIsLooong Ready for re-review.

void selectAll(SelectionChangedCause cause) {
textSelectionDelegate.selectAll(cause);
/// Returns the TextPosition above or below the given offset.
TextPosition getTextPositionVertical(TextPosition position, double verticalOffset) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be private I think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yup good catch.

}

/// Return the selection collapsed and moved to the given [TextPosition].
TextSelection moveTo(TextPosition position) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this just TextSelection.fromPosition, except this preserves the isDirectional property?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point, which as we've discussed doesn't seem to be relevant to this. I'll swap in fromPosition and get rid of this method.

///
/// Setting includeWhitespace to false will only return the index of non-space
/// characters.
static int nextCharacter(int index, String string, [bool includeWhitespace = true]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CharacterRange.at constructor does handle that case according to its documentation https://master-api.flutter.dev/flutter/characters/CharacterRange/CharacterRange.at.html, and its implementation seems to be more efficient than iterating through the whole document:
https://github.com/dart-lang/characters/blob/e7406a17e1318e702b824d0f47db131349ff9ee0/lib/src/characters_impl.dart#L447-L452

convert to using Characters everywhere

I think one of the current road blocker is that both our text layout libs and most of our platform input plugins only take UTF16 code points so converting the indices (efficiently) is a bit of a problem.

///
/// Includes newline characters from ASCII and separators from the
/// [unicode separator category](https://www.compart.com/en/unicode/category/Zs)
static bool isWhitespace(int codeUnit) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be a more reasonable place to place the utility method. But I wonder if it's possible to not expose this (at least not until we expose ICU methods) for now?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like it's used in 3 different files now unfortunately (RenderEditable, TextEditingValue, TextEditingActionTarget). Probably not worth it to duplicate the method three times.

I see @gspencergoog's comment about this:

// TODO(gspencergoog): replace when we expose this ICU information.

Any idea if we still have plans to expose that, or if there's a cleaner way for me to expose this one?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know if we plan to expose it, but I think the problem was that it adds to executable size too much to add the tables in that ICU uses.

/// {@template flutter.services.TextMetrics.getLineAtOffset}
/// Return a [TextSelection] containing the line of the given [TextPosition].
/// {@endtemplate}
TextSelection getLineAtOffset(String text, TextPosition position);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO this is going to be error prone. TextMetrics's layout information is highly dependent on its text, so being able to provide a potentially different String is a bit confusing.

enforce what text it's using

Yeah but we can't layout a plain String. It has to be styled and you need other inputs for instance the incoming box constraints. Also other methods in the interface don't seem to take a String? All TextMetrics implementations are likely going to have states.

And for RenderEditable.getLineAtOffset, getting the length of the obscured text should be an implementation detail instead of being exposed as a parameter I think.

}
/// The current text, selection, and composing state for editing a run of text.
@immutable
class TextEditingValue {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah the android Editable implementation implements most common text manipulation methods in terms of replace, since it's replace implementation takes care of the selection and composition.

Copy link
Contributor

@LongCatIsLooong LongCatIsLooong left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. I wonder if there's a better name for TextMetrics. TextLayout?

@justinmc justinmc merged commit 2382b4c into flutter:master Sep 7, 2021
@justinmc justinmc deleted the text-editing-model branch September 7, 2021 21:12
cgestes added a commit to cgestes/zefyr that referenced this pull request Oct 14, 2021
cgestes added a commit to cgestes/zefyr that referenced this pull request Oct 17, 2021
cgestes added a commit to cgestes/zefyr that referenced this pull request Oct 19, 2021
cgestes added a commit to cgestes/zefyr that referenced this pull request Oct 20, 2021
cgestes added a commit to cgestes/zefyr that referenced this pull request Oct 22, 2021
cgestes added a commit to cgestes/zefyr that referenced this pull request Nov 10, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
f: cupertino flutter/packages/flutter/cupertino repository f: material design flutter/packages/flutter/material repository. framework flutter/packages/flutter repository. See also f: labels.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants