Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions packages/devtools_app/lib/src/shared/editor/api_classes.dart
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ abstract class Field {
static const documentation = 'documentation';
static const emulator = 'emulator';
static const emulatorId = 'emulatorId';
static const end = 'end';
static const ephemeral = 'ephemeral';
static const errorText = 'errorText';
static const flutterDeviceId = 'flutterDeviceId';
Expand All @@ -122,10 +123,12 @@ abstract class Field {
static const platformType = 'platformType';
static const prefersDebugSession = 'prefersDebugSession';
static const projectRootPath = 'projectRootPath';
static const range = 'range';
static const requiresDebugSession = 'requiresDebugSession';
static const result = 'result';
static const selectedDeviceId = 'selectedDeviceId';
static const selections = 'selections';
static const start = 'start';
static const supported = 'supported';
static const supportsForceExternal = 'supportsForceExternal';
static const textDocument = 'textDocument';
Expand Down Expand Up @@ -393,6 +396,31 @@ class EditorSelection with Serializable {
};
}

/// A range in the editor expressed as (zero-based) start and end positions.
class EditorRange with Serializable {
EditorRange({required this.start, required this.end});

EditorRange.fromJson(Map<String, Object?> map)
: this(
start: CursorPosition.fromJson(
map[Field.start] as Map<String, Object?>,
),
end: CursorPosition.fromJson(map[Field.end] as Map<String, Object?>),
);

/// The range's start position.
final CursorPosition start;

/// The range's end position.
final CursorPosition end;

@override
Map<String, Object?> toJson() => {
Field.start: start.toJson(),
Field.end: end.toJson(),
};
}

/// Representation of a single cursor position in the editor.
///
/// The cursor position is after the given [character] of the [line].
Expand Down Expand Up @@ -430,12 +458,23 @@ class CursorPosition with Serializable {

/// The result of an `editableArguments` request.
class EditableArgumentsResult with Serializable {
EditableArgumentsResult({required this.args, this.name, this.documentation});
EditableArgumentsResult({
required this.args,
this.name,
this.documentation,
this.range,
});

EditableArgumentsResult.fromJson(Map<String, Object?> map)
: this(
name: map[Field.name] as String?,
documentation: map[Field.documentation] as String?,
range:
(map[Field.range] as Map<String, Object?>?) == null
? null
: EditorRange.fromJson(
map[Field.range] as Map<String, Object?>,
),
args:
(map[Field.arguments] as List<Object?>? ?? <Object?>[])
.cast<Map<String, Object?>>()
Expand All @@ -446,9 +485,15 @@ class EditableArgumentsResult with Serializable {
final List<EditableArgument> args;
final String? name;
final String? documentation;
final EditorRange? range;

@override
Map<String, Object?> toJson() => {Field.arguments: args};
Map<String, Object?> toJson() => {
Field.arguments: args,
Field.name: name,
Field.documentation: documentation,
Field.range: range,
};
}

/// Errors that the Analysis Server returns for failed argument edits.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ typedef EditableWidgetData =
String? name,
String? documentation,
String? fileUri,
EditorRange? range,
});

typedef EditArgumentFunction =
Expand Down Expand Up @@ -51,6 +52,7 @@ class PropertyEditorController extends DisposableController
String? get widgetName => _editableWidgetData.value?.name;
String? get widgetDocumentation => _editableWidgetData.value?.documentation;
String? get fileUri => _editableWidgetData.value?.fileUri;
EditorRange? get widgetRange => _editableWidgetData.value?.range;

ValueListenable<bool> get shouldReconnect => _shouldReconnect;
final _shouldReconnect = ValueNotifier<bool>(false);
Expand Down Expand Up @@ -100,6 +102,7 @@ class PropertyEditorController extends DisposableController
properties: [],
name: null,
documentation: null,
range: null,
fileUri: textDocument.uriAsString,
);
return;
Expand Down Expand Up @@ -147,6 +150,30 @@ class PropertyEditorController extends DisposableController
);
}

int hashProperty(EditableProperty property) {
final widgetData = editableWidgetData.value;
if (widgetData == null) {
return Object.hash(property.name, property.type);
}
final range = widgetRange;
return range == null
? Object.hash(
property.name,
property.type,
property.value, // Include the property value.
widgetName,
fileUri,
)
: Object.hash(
property.name,
property.type,
fileUri,
widgetName,
range.start.line, // Include the start position of the property.
range.start.character,
);
}

Future<void> _updateWithEditableArgs({
required TextDocument textDocument,
required CursorPosition cursorPosition,
Expand All @@ -166,11 +193,14 @@ class PropertyEditorController extends DisposableController
.where((property) => !property.isDeprecated || property.hasArgument)
.toList();
final name = result?.name;
final range = result?.range;

_editableWidgetData.value = (
properties: properties,
name: name,
documentation: result?.documentation,
fileUri: _currentDocument?.uriAsString,
range: range,
);
filterData(activeFilter.value);
// Register impression.
Expand All @@ -195,6 +225,7 @@ class PropertyEditorController extends DisposableController
EditableArgumentsResult? editableArgsResult,
TextDocument? document,
CursorPosition? cursorPosition,
EditorRange? range,
}) {
setActiveFilter();
if (editableArgsResult != null) {
Expand All @@ -204,6 +235,7 @@ class PropertyEditorController extends DisposableController
name: editableArgsResult.name,
documentation: editableArgsResult.documentation,
fileUri: document?.uriAsString,
range: range,
);
}
if (document != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -208,13 +208,16 @@ class _TextInputState<T> extends State<_TextInput<T>>

late final FocusNode _focusNode;

late final TextEditingController _controller;

late String _currentValue;

@override
void initState() {
super.initState();
_currentValue = widget.property.valueDisplay;
_focusNode = FocusNode(debugLabel: 'text-input-${widget.property.name}');
_controller = TextEditingController(text: widget.property.valueDisplay);

addAutoDisposeListener(_focusNode, () async {
if (_focusNode.hasFocus) return;
Expand All @@ -223,12 +226,20 @@ class _TextInputState<T> extends State<_TextInput<T>>
});
}

@override
void didUpdateWidget(_TextInput<T> oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.property != widget.property) {
_setValueAndMaintainSelection(widget.property.valueDisplay);
}
}

@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
return TextFormField(
focusNode: _focusNode,
initialValue: widget.property.valueDisplay,
controller: _controller,
enabled: widget.property.isEditable,
autovalidateMode: AutovalidateMode.onUserInteraction,
validator: (text) => inputValidator(text, property: widget.property),
Expand Down Expand Up @@ -259,6 +270,28 @@ class _TextInputState<T> extends State<_TextInput<T>>
editPropertyCallback: widget.editProperty,
);
}

/// Sets the text field's value to [newValue].
///
/// Determines what the correct text selection should be based on the previous
/// selection. Without this, the entire text field contents would be selected
/// after editing a property. For details, see:
/// https://github.com/flutter/flutter/issues/161596
void _setValueAndMaintainSelection(String newValue) {
final previousSelection = _controller.selection;
// If the previous selection is in range of the new text, use it. Otherwise,
// set the empty selection at the end of the string.
final newSelection =
(newValue.length < previousSelection.end ||
newValue.length < previousSelection.start)
? TextSelection.collapsed(offset: newValue.length)
: previousSelection;
// Set the new value in the controller with the new selection.
_controller.value = TextEditingValue(
text: newValue,
selection: newSelection,
);
}
}

mixin _PropertyInputMixin<T extends StatefulWidget, U> on State<T> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ class PropertyEditorView extends StatelessWidget {
return [introSentence, const HowToUseMessage()];
}

final (:properties, :name, :documentation, :fileUri) = editableWidgetData;
final (:properties, :name, :documentation, :fileUri, :range) =
editableWidgetData;
if (fileUri != null && !fileUri.endsWith('.dart')) {
return [const NoDartCodeMessage(), const HowToUseMessage()];
}
Expand Down Expand Up @@ -123,8 +124,7 @@ class _PropertiesListState extends State<_PropertiesList> {
for (final property in properties)
_EditablePropertyItem(
property: property,
editProperty: widget.controller.editArgument,
widgetDocumentation: widget.controller.widgetDocumentation,
controller: widget.controller,
),
].joinWith(const PaddedDivider.noPadding()),
);
Expand All @@ -136,13 +136,11 @@ class _PropertiesListState extends State<_PropertiesList> {
class _EditablePropertyItem extends StatelessWidget {
const _EditablePropertyItem({
required this.property,
required this.editProperty,
required this.widgetDocumentation,
required this.controller,
});

final EditableProperty property;
final EditArgumentFunction editProperty;
final String? widgetDocumentation;
final PropertyEditorController controller;

@override
Widget build(BuildContext context) {
Expand All @@ -162,13 +160,13 @@ class _EditablePropertyItem extends StatelessWidget {
),
child: _InfoTooltip(
property: property,
widgetDocumentation: widgetDocumentation,
widgetDocumentation: controller.widgetDocumentation,
),
),
Expanded(
child: _PropertyInput(
property: property,
editProperty: editProperty,
controller: controller,
),
),
],
Expand Down Expand Up @@ -354,16 +352,16 @@ class _InfoTooltip extends StatelessWidget {
}

class _PropertyInput extends StatelessWidget {
const _PropertyInput({required this.property, required this.editProperty});
const _PropertyInput({required this.property, required this.controller});

final EditableProperty property;
final EditArgumentFunction editProperty;
final PropertyEditorController controller;

@override
Widget build(BuildContext context) {
final argType = property.type;
final propertyKey = Key(property.hashCode.toString());
switch (argType) {
final editProperty = controller.editArgument;
final propertyKey = Key(controller.hashProperty(property).toString());
switch (property.type) {
case boolType:
return BooleanInput(
key: propertyKey,
Expand Down
Loading