Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
b970f406
Unverified
Commit
b970f406
authored
May 11, 2021
by
LongCatIsLooong
Committed by
GitHub
May 11, 2021
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Let RenderEditable.delete directly delete from the controller text. (#82215)
parent
5ebf4885
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
186 additions
and
35 deletions
+186
-35
editable.dart
packages/flutter/lib/src/rendering/editable.dart
+106
-35
editable_test.dart
packages/flutter/test/rendering/editable_test.dart
+80
-0
No files found.
packages/flutter/lib/src/rendering/editable.dart
View file @
b970f406
...
...
@@ -1011,9 +1011,9 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
return
_getTextPositionVertical
(
offset
,
verticalOffset
);
}
// Deletes the
current uncollapsed selection
.
// Deletes the
text within `selection` if it's non-empty
.
void
_deleteSelection
(
TextSelection
selection
,
SelectionChangedCause
cause
)
{
assert
(
selection
.
isCollapsed
==
false
);
assert
(
!
selection
.
isCollapsed
);
if
(
_readOnly
||
!
selection
.
isValid
||
selection
.
isCollapsed
)
{
return
;
...
...
@@ -1023,7 +1023,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
final
String
textBefore
=
selection
.
textBefore
(
text
);
final
String
textAfter
=
selection
.
textAfter
(
text
);
final
int
cursorPosition
=
math
.
min
(
selection
.
start
,
selection
.
end
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
cursorPosition
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
),
...
...
@@ -1031,6 +1030,48 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
);
}
// Deletes the current non-empty selection.
//
// Operates on the text/selection contained in textSelectionDelegate, and does
// not depend on `RenderEditable.selection`.
//
// If the selection is currently non-empty, this method deletes the selected
// text and returns true. Otherwise this method does nothing and returns
// false.
bool
_deleteNonEmptySelection
(
SelectionChangedCause
cause
)
{
// TODO(LongCatIsLooong): remove this method from `RenderEditable`
// https://github.com/flutter/flutter/issues/80226.
assert
(!
readOnly
);
final
TextEditingValue
controllerValue
=
textSelectionDelegate
.
textEditingValue
;
final
TextSelection
selection
=
controllerValue
.
selection
;
assert
(
selection
.
isValid
);
if
(
selection
.
isCollapsed
)
{
return
false
;
}
final
String
textBefore
=
selection
.
textBefore
(
controllerValue
.
text
);
final
String
textAfter
=
selection
.
textAfter
(
controllerValue
.
text
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
selection
.
start
);
final
TextRange
composing
=
controllerValue
.
composing
;
final
TextRange
newComposingRange
=
!
composing
.
isValid
||
composing
.
isCollapsed
?
TextRange
.
empty
:
TextRange
(
start:
composing
.
start
-
(
composing
.
start
-
selection
.
start
).
clamp
(
0
,
selection
.
end
-
selection
.
start
),
end:
composing
.
end
-
(
composing
.
end
-
selection
.
start
).
clamp
(
0
,
selection
.
end
-
selection
.
start
),
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
,
composing:
newComposingRange
,
),
cause
,
);
return
true
;
}
// Deletes the from the current collapsed selection to the start of the field.
//
// The given SelectionChangedCause indicates the cause of this change and
...
...
@@ -1089,12 +1130,15 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
);
}
/// Deletes backwards from the
current selection
.
/// Deletes backwards from the
selection in [textSelectionDelegate]
.
///
/// If the [selection] is collapsed, deletes a single character before the
/// This method operates on the text/selection contained in
/// [textSelectionDelegate], and does not depend on [selection].
///
/// If the selection is collapsed, deletes a single character before the
/// cursor.
///
/// If the
[selection]
is not collapsed, deletes the selection.
/// If the
selection
is not collapsed, deletes the selection.
///
/// {@template flutter.rendering.RenderEditable.cause}
/// The given [SelectionChangedCause] indicates the cause of this change and
...
...
@@ -1105,29 +1149,45 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
///
/// * [deleteForward], which is same but in the opposite direction.
void
delete
(
SelectionChangedCause
cause
)
{
assert
(
_selection
!=
null
);
// `delete` does not depend on the text layout, and the boundary analysis is
// done using the `previousCharacter` method instead of ICU, we can keep
// deleting without having to layout the text. For this reason, we can
// directly delete the character before the caret in the controller.
//
// TODO(LongCatIsLooong): remove this method from RenderEditable.
// https://github.com/flutter/flutter/issues/80226.
final
TextEditingValue
controllerValue
=
textSelectionDelegate
.
textEditingValue
;
final
TextSelection
selection
=
controllerValue
.
selection
;
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
if
(
!
selection
.
isValid
||
readOnly
||
_deleteNonEmptySelection
(
cause
)
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
String
textBefore
=
_selection
!.
textBefore
(
text
);
assert
(
selection
.
isCollapsed
);
final
String
textBefore
=
selection
.
textBefore
(
controllerValue
.
text
);
if
(
textBefore
.
isEmpty
)
{
return
;
}
final
int
characterBoundary
=
previousCharacter
(
textBefore
.
length
,
textBefore
);
textBefore
=
textBefore
.
substring
(
0
,
characterBoundary
);
final
String
textAfter
=
selection
.
textAfter
(
controllerValue
.
text
);
final
String
textAfter
=
_selection
!.
textAfter
(
text
);
final
int
characterBoundary
=
previousCharacter
(
textBefore
.
length
,
textBefore
);
final
TextSelection
newSelection
=
TextSelection
.
collapsed
(
offset:
characterBoundary
);
final
TextRange
composing
=
controllerValue
.
composing
;
assert
(
textBefore
.
length
>=
characterBoundary
);
final
TextRange
newComposingRange
=
!
composing
.
isValid
||
composing
.
isCollapsed
?
TextRange
.
empty
:
TextRange
(
start:
composing
.
start
-
(
composing
.
start
-
characterBoundary
).
clamp
(
0
,
textBefore
.
length
-
characterBoundary
),
end:
composing
.
end
-
(
composing
.
end
-
characterBoundary
).
clamp
(
0
,
textBefore
.
length
-
characterBoundary
),
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
newSelection
),
TextEditingValue
(
text:
textBefore
.
substring
(
0
,
characterBoundary
)
+
textAfter
,
selection:
newSelection
,
composing:
newComposingRange
,
),
cause
,
);
}
...
...
@@ -1238,12 +1298,16 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
);
}
/// Deletes in the foward direction from the current selection.
/// Deletes in the foward direction, from the current selection in
/// [textSelectionDelegate].
///
/// If the [selection] is collapsed, deletes a single character after the
/// This method operates on the text/selection contained in
/// [textSelectionDelegate], and does not depend on [selection].
///
/// If the selection is collapsed, deletes a single character after the
/// cursor.
///
/// If the
[selection]
is not collapsed, deletes the selection.
/// If the
selection
is not collapsed, deletes the selection.
///
/// {@macro flutter.rendering.RenderEditable.cause}
///
...
...
@@ -1251,29 +1315,36 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin {
///
/// * [delete], which is same but in the opposite direction.
void
deleteForward
(
SelectionChangedCause
cause
)
{
assert
(
_selection
!=
null
);
// TODO(LongCatIsLooong): remove this method from RenderEditable.
// https://github.com/flutter/flutter/issues/80226.
final
TextEditingValue
controllerValue
=
textSelectionDelegate
.
textEditingValue
;
final
TextSelection
selection
=
controllerValue
.
selection
;
if
(
_readOnly
||
!
_selection
!.
isValid
)
{
if
(
!
selection
.
isValid
||
_readOnly
||
_deleteNonEmptySelection
(
cause
)
)
{
return
;
}
if
(!
_selection
!.
isCollapsed
)
{
return
_deleteSelection
(
_selection
!,
cause
);
}
final
String
text
=
textSelectionDelegate
.
textEditingValue
.
text
;
final
String
textBefore
=
_selection
!.
textBefore
(
text
);
String
textAfter
=
_selection
!.
textAfter
(
text
);
assert
(
selection
.
isCollapsed
);
final
String
textAfter
=
selection
.
textAfter
(
controllerValue
.
text
);
if
(
textAfter
.
isEmpty
)
{
return
;
}
final
int
deleteCount
=
nextCharacter
(
0
,
textAfter
);
textAfter
=
textAfter
.
substring
(
deleteCount
);
final
String
textBefore
=
selection
.
textBefore
(
controllerValue
.
text
);
final
int
characterBoundary
=
nextCharacter
(
0
,
textAfter
);
final
TextRange
composing
=
controllerValue
.
composing
;
final
TextRange
newComposingRange
=
!
composing
.
isValid
||
composing
.
isCollapsed
?
TextRange
.
empty
:
TextRange
(
start:
composing
.
start
-
(
composing
.
start
-
textBefore
.
length
).
clamp
(
0
,
characterBoundary
),
end:
composing
.
end
-
(
composing
.
end
-
textBefore
.
length
).
clamp
(
0
,
characterBoundary
),
);
_setTextEditingValue
(
TextEditingValue
(
text:
textBefore
+
textAfter
,
selection:
_selection
!),
TextEditingValue
(
text:
textBefore
+
textAfter
.
substring
(
characterBoundary
),
selection:
selection
,
composing:
newComposingRange
,
),
cause
,
);
}
...
...
packages/flutter/test/rendering/editable_test.dart
View file @
b970f406
...
...
@@ -3463,6 +3463,86 @@ void main() {
});
});
});
group
(
'delete API implementations'
,
()
{
// Regression test for: https://github.com/flutter/flutter/issues/80226.
//
// This textSelectionDelegate has different text and selection from the
// render editable.
final
FakeEditableTextState
delegate
=
FakeEditableTextState
();
late
RenderEditable
editable
;
setUp
(()
{
editable
=
RenderEditable
(
text:
TextSpan
(
text:
'A '
*
50
,
),
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
textDirection:
TextDirection
.
ltr
,
offset:
ViewportOffset
.
fixed
(
0
),
textSelectionDelegate:
delegate
,
selection:
const
TextSelection
(
baseOffset:
0
,
extentOffset:
50
),
);
delegate
.
textEditingValue
=
const
TextEditingValue
(
text:
'BBB'
,
selection:
TextSelection
.
collapsed
(
offset:
0
),
);
});
void
verifyDoesNotCrashWithInconsistentTextEditingValue
(
void
Function
(
SelectionChangedCause
)
method
)
{
editable
=
RenderEditable
(
text:
TextSpan
(
text:
'A '
*
50
,
),
startHandleLayerLink:
LayerLink
(),
endHandleLayerLink:
LayerLink
(),
textDirection:
TextDirection
.
ltr
,
offset:
ViewportOffset
.
fixed
(
0
),
textSelectionDelegate:
delegate
,
selection:
const
TextSelection
(
baseOffset:
0
,
extentOffset:
50
),
);
layout
(
editable
,
constraints:
BoxConstraints
.
loose
(
const
Size
(
500.0
,
500.0
)));
dynamic
error
;
try
{
method
(
SelectionChangedCause
.
tap
);
}
catch
(
e
)
{
error
=
e
;
}
expect
(
error
,
isNull
);
}
test
(
'delete is not racy and handles composing region correctly'
,
()
{
delegate
.
textEditingValue
=
const
TextEditingValue
(
text:
'ABCDEF'
,
selection:
TextSelection
.
collapsed
(
offset:
2
),
composing:
TextRange
(
start:
1
,
end:
6
),
);
verifyDoesNotCrashWithInconsistentTextEditingValue
(
editable
.
delete
);
final
TextEditingValue
textEditingValue
=
editable
.
textSelectionDelegate
.
textEditingValue
;
expect
(
textEditingValue
.
text
,
'ACDEF'
);
expect
(
textEditingValue
.
selection
.
isCollapsed
,
isTrue
);
expect
(
textEditingValue
.
selection
.
baseOffset
,
1
);
expect
(
textEditingValue
.
composing
,
const
TextRange
(
start:
1
,
end:
5
));
});
test
(
'deleteForward is not racy and handles composing region correctly'
,
()
{
delegate
.
textEditingValue
=
const
TextEditingValue
(
text:
'ABCDEF'
,
selection:
TextSelection
.
collapsed
(
offset:
2
),
composing:
TextRange
(
start:
2
,
end:
6
),
);
verifyDoesNotCrashWithInconsistentTextEditingValue
(
editable
.
deleteForward
);
final
TextEditingValue
textEditingValue
=
editable
.
textSelectionDelegate
.
textEditingValue
;
expect
(
textEditingValue
.
text
,
'ABDEF'
);
expect
(
textEditingValue
.
selection
.
isCollapsed
,
isTrue
);
expect
(
textEditingValue
.
selection
.
baseOffset
,
2
);
expect
(
textEditingValue
.
composing
,
const
TextRange
(
start:
2
,
end:
5
));
});
});
}
class
_TestRenderEditable
extends
RenderEditable
{
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment