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
c135cd34
Unverified
Commit
c135cd34
authored
May 27, 2022
by
Justin McCandless
Committed by
GitHub
May 27, 2022
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
MacOS transpose keyboard shortcut (#104457)
Implements ctrl-T to transpose characters on Mac and iOS
parent
15308b33
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
245 additions
and
1 deletion
+245
-1
default_text_editing_shortcuts.dart
...utter/lib/src/widgets/default_text_editing_shortcuts.dart
+2
-0
editable_text.dart
packages/flutter/lib/src/widgets/editable_text.dart
+44
-1
text_editing_intents.dart
packages/flutter/lib/src/widgets/text_editing_intents.dart
+7
-0
editable_text_test.dart
packages/flutter/test/widgets/editable_text_test.dart
+192
-0
No files found.
packages/flutter/lib/src/widgets/default_text_editing_shortcuts.dart
View file @
c135cd34
...
...
@@ -299,6 +299,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
const
SingleActivator
(
LogicalKeyboardKey
.
arrowUp
,
shift:
true
,
meta:
true
):
const
ExtendSelectionToDocumentBoundaryIntent
(
forward:
false
,
collapseSelection:
false
),
const
SingleActivator
(
LogicalKeyboardKey
.
arrowDown
,
shift:
true
,
meta:
true
):
const
ExtendSelectionToDocumentBoundaryIntent
(
forward:
true
,
collapseSelection:
false
),
const
SingleActivator
(
LogicalKeyboardKey
.
keyT
,
control:
true
):
const
TransposeCharactersIntent
(),
const
SingleActivator
(
LogicalKeyboardKey
.
home
):
const
ScrollToDocumentBoundaryIntent
(
forward:
false
),
const
SingleActivator
(
LogicalKeyboardKey
.
end
):
const
ScrollToDocumentBoundaryIntent
(
forward:
true
),
const
SingleActivator
(
LogicalKeyboardKey
.
home
,
shift:
true
):
const
ExpandSelectionToDocumentBoundaryIntent
(
forward:
false
),
...
...
packages/flutter/lib/src/widgets/editable_text.dart
View file @
c135cd34
...
...
@@ -3223,6 +3223,47 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
return
Action
<
T
>.
overridable
(
context:
context
,
defaultAction:
defaultAction
);
}
/// Transpose the characters immediately before and after the current
/// collapsed selection.
///
/// When the cursor is at the end of the text, transposes the last two
/// characters, if they exist.
///
/// When the cursor is at the start of the text, does nothing.
void
_transposeCharacters
(
TransposeCharactersIntent
intent
)
{
if
(
_value
.
text
.
characters
.
length
<=
1
||
_value
.
selection
==
null
||
!
_value
.
selection
.
isCollapsed
||
_value
.
selection
.
baseOffset
==
0
)
{
return
;
}
final
String
text
=
_value
.
text
;
final
TextSelection
selection
=
_value
.
selection
;
final
bool
atEnd
=
selection
.
baseOffset
==
text
.
length
;
final
CharacterRange
transposing
=
CharacterRange
.
at
(
text
,
selection
.
baseOffset
);
if
(
atEnd
)
{
transposing
.
moveBack
(
2
);
}
else
{
transposing
..
moveBack
()..
expandNext
();
}
assert
(
transposing
.
currentCharacters
.
length
==
2
);
userUpdateTextEditingValue
(
TextEditingValue
(
text:
transposing
.
stringBefore
+
transposing
.
currentCharacters
.
last
+
transposing
.
currentCharacters
.
first
+
transposing
.
stringAfter
,
selection:
TextSelection
.
collapsed
(
offset:
transposing
.
stringBeforeLength
+
transposing
.
current
.
length
,
),
),
SelectionChangedCause
.
keyboard
,
);
}
late
final
Action
<
TransposeCharactersIntent
>
_transposeCharactersAction
=
CallbackAction
<
TransposeCharactersIntent
>(
onInvoke:
_transposeCharacters
);
void
_replaceText
(
ReplaceTextIntent
intent
)
{
final
TextEditingValue
oldValue
=
_value
;
final
TextEditingValue
newValue
=
intent
.
currentTextEditingValue
.
replaced
(
...
...
@@ -3317,7 +3358,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
DeleteToLineBreakIntent:
_makeOverridable
(
_DeleteTextAction
<
DeleteToLineBreakIntent
>(
this
,
_linebreak
)),
// Extend/Move Selection
ExtendSelectionByCharacterIntent:
_makeOverridable
(
_UpdateTextSelectionAction
<
ExtendSelectionByCharacterIntent
>(
this
,
false
,
_characterBoundary
,
)),
ExtendSelectionByCharacterIntent:
_makeOverridable
(
_UpdateTextSelectionAction
<
ExtendSelectionByCharacterIntent
>(
this
,
false
,
_characterBoundary
)),
ExtendSelectionToNextWordBoundaryIntent:
_makeOverridable
(
_UpdateTextSelectionAction
<
ExtendSelectionToNextWordBoundaryIntent
>(
this
,
true
,
_nextWordBoundary
)),
ExtendSelectionToLineBreakIntent:
_makeOverridable
(
_UpdateTextSelectionAction
<
ExtendSelectionToLineBreakIntent
>(
this
,
true
,
_linebreak
)),
ExpandSelectionToLineBreakIntent:
_makeOverridable
(
CallbackAction
<
ExpandSelectionToLineBreakIntent
>(
onInvoke:
_expandSelectionToLinebreak
)),
...
...
@@ -3331,6 +3372,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
SelectAllTextIntent:
_makeOverridable
(
_SelectAllAction
(
this
)),
CopySelectionTextIntent:
_makeOverridable
(
_CopySelectionAction
(
this
)),
PasteTextIntent:
_makeOverridable
(
CallbackAction
<
PasteTextIntent
>(
onInvoke:
(
PasteTextIntent
intent
)
=>
pasteText
(
intent
.
cause
))),
TransposeCharactersIntent:
_makeOverridable
(
_transposeCharactersAction
),
};
@override
...
...
packages/flutter/lib/src/widgets/text_editing_intents.dart
View file @
c135cd34
...
...
@@ -329,3 +329,10 @@ class UpdateSelectionIntent extends Intent {
/// {@macro flutter.widgets.TextEditingIntents.cause}
final
SelectionChangedCause
cause
;
}
/// An [Intent] that represents a user interaction that attempts to swap the
/// characters immediately around the cursor.
class
TransposeCharactersIntent
extends
Intent
{
/// Creates a [TransposeCharactersIntent].
const
TransposeCharactersIntent
();
}
packages/flutter/test/widgets/editable_text_test.dart
View file @
c135cd34
...
...
@@ -12339,6 +12339,198 @@ void main() {
skip:
kIsWeb
,
// [intended] on web these keys are handled by the browser.
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}),
);
group
(
'ctrl-T to transpose'
,
()
{
Future
<
void
>
ctrlT
(
WidgetTester
tester
,
String
platform
)
async
{
await
tester
.
sendKeyDownEvent
(
LogicalKeyboardKey
.
controlLeft
,
platform:
platform
,
);
await
tester
.
pump
();
await
tester
.
sendKeyEvent
(
LogicalKeyboardKey
.
keyT
,
platform:
platform
);
await
tester
.
pump
();
await
tester
.
sendKeyUpEvent
(
LogicalKeyboardKey
.
controlLeft
,
platform:
platform
);
await
tester
.
pump
();
}
testWidgets
(
'with normal characters'
,
(
WidgetTester
tester
)
async
{
final
String
targetPlatformString
=
defaultTargetPlatform
.
toString
();
final
String
platform
=
targetPlatformString
.
substring
(
targetPlatformString
.
indexOf
(
'.'
)
+
1
).
toLowerCase
();
final
TextEditingController
controller
=
TextEditingController
(
text:
testText
);
controller
.
selection
=
const
TextSelection
(
baseOffset:
0
,
extentOffset:
0
,
affinity:
TextAffinity
.
upstream
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Align
(
alignment:
Alignment
.
topLeft
,
child:
SizedBox
(
width:
400
,
child:
EditableText
(
maxLines:
10
,
controller:
controller
,
showSelectionHandles:
true
,
autofocus:
true
,
focusNode:
FocusNode
(),
style:
Typography
.
material2018
().
black
.
subtitle1
!,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
selectionControls:
materialTextSelectionControls
,
keyboardType:
TextInputType
.
text
,
textAlign:
TextAlign
.
right
,
),
),
),
));
await
tester
.
pump
();
// Wait for autofocus to take effect.
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
0
);
// ctrl-T does nothing at the start of the field.
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
0
);
controller
.
selection
=
const
TextSelection
(
baseOffset:
1
,
extentOffset:
4
,
);
await
tester
.
pump
();
expect
(
controller
.
selection
.
isCollapsed
,
isFalse
);
expect
(
controller
.
selection
.
baseOffset
,
1
);
expect
(
controller
.
selection
.
extentOffset
,
4
);
// ctrl-T does nothing when the selection isn't collapsed.
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isFalse
);
expect
(
controller
.
selection
.
baseOffset
,
1
);
expect
(
controller
.
selection
.
extentOffset
,
4
);
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
5
);
await
tester
.
pump
();
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
5
);
// ctrl-T swaps the previous and next characters when they exist.
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
6
);
expect
(
controller
.
text
.
substring
(
0
,
19
),
'Now si the time for'
);
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
7
);
expect
(
controller
.
text
.
substring
(
0
,
19
),
'Now s ithe time for'
);
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
8
);
expect
(
controller
.
text
.
substring
(
0
,
19
),
'Now s tihe time for'
);
controller
.
selection
=
TextSelection
.
collapsed
(
offset:
controller
.
text
.
length
,
);
await
tester
.
pump
();
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
controller
.
text
.
length
);
expect
(
controller
.
text
.
substring
(
55
,
72
),
'of their country.'
);
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
controller
.
text
.
length
);
expect
(
controller
.
text
.
substring
(
55
,
72
),
'of their countr.y'
);
},
skip:
kIsWeb
,
// [intended] on web these keys are handled by the browser.
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}),
);
testWidgets
(
'with extended grapheme clusters'
,
(
WidgetTester
tester
)
async
{
final
String
targetPlatformString
=
defaultTargetPlatform
.
toString
();
final
String
platform
=
targetPlatformString
.
substring
(
targetPlatformString
.
indexOf
(
'.'
)
+
1
).
toLowerCase
();
final
TextEditingController
controller
=
TextEditingController
(
// One extended grapheme cluster of length 8 and one surrogate pair of
// length 2.
text:
'👨👩👦😆'
,
);
controller
.
selection
=
const
TextSelection
(
baseOffset:
0
,
extentOffset:
0
,
affinity:
TextAffinity
.
upstream
,
);
await
tester
.
pumpWidget
(
MaterialApp
(
home:
Align
(
alignment:
Alignment
.
topLeft
,
child:
SizedBox
(
width:
400
,
child:
EditableText
(
maxLines:
10
,
controller:
controller
,
showSelectionHandles:
true
,
autofocus:
true
,
focusNode:
FocusNode
(),
style:
Typography
.
material2018
().
black
.
subtitle1
!,
cursorColor:
Colors
.
blue
,
backgroundCursorColor:
Colors
.
grey
,
selectionControls:
materialTextSelectionControls
,
keyboardType:
TextInputType
.
text
,
textAlign:
TextAlign
.
right
,
),
),
),
));
await
tester
.
pump
();
// Wait for autofocus to take effect.
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
0
);
// ctrl-T does nothing at the start of the field.
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
0
);
expect
(
controller
.
text
,
'👨👩👦😆'
);
controller
.
selection
=
const
TextSelection
(
baseOffset:
8
,
extentOffset:
10
,
);
await
tester
.
pump
();
expect
(
controller
.
selection
.
isCollapsed
,
isFalse
);
expect
(
controller
.
selection
.
baseOffset
,
8
);
expect
(
controller
.
selection
.
extentOffset
,
10
);
// ctrl-T does nothing when the selection isn't collapsed.
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isFalse
);
expect
(
controller
.
selection
.
baseOffset
,
8
);
expect
(
controller
.
selection
.
extentOffset
,
10
);
expect
(
controller
.
text
,
'👨👩👦😆'
);
controller
.
selection
=
const
TextSelection
.
collapsed
(
offset:
8
);
await
tester
.
pump
();
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
8
);
// ctrl-T swaps the previous and next characters when they exist.
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
10
);
expect
(
controller
.
text
,
'😆👨👩👦'
);
await
ctrlT
(
tester
,
platform
);
expect
(
controller
.
selection
.
isCollapsed
,
isTrue
);
expect
(
controller
.
selection
.
baseOffset
,
10
);
expect
(
controller
.
text
,
'👨👩👦😆'
);
},
skip:
kIsWeb
,
// [intended] on web these keys are handled by the browser.
variant:
const
TargetPlatformVariant
(<
TargetPlatform
>{
TargetPlatform
.
iOS
,
TargetPlatform
.
macOS
}),
);
});
});
}
...
...
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