Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ class _CodeEditorControlState extends State<CodeEditorControl> {
TextSelection? _selection;
String _value = "";
String? _languageName;
String? _lastControlValue;
TextSelection? _lastControlSelection;

@override
void initState() {
Expand All @@ -31,6 +33,12 @@ class _CodeEditorControlState extends State<CodeEditorControl> {
_controller = _createController();
_value = _readValue();
_selection = _controller.selection;
_lastControlValue = widget.control.getString("value");
_lastControlSelection = widget.control.getTextSelection(
"selection",
minOffset: 0,
maxOffset: _controller.text.length,
);
_controller.addListener(_handleControllerChange);
widget.control.addInvokeMethodListener(_invokeMethod);
}
Expand Down Expand Up @@ -97,6 +105,20 @@ class _CodeEditorControlState extends State<CodeEditorControl> {
_controller.fullText = value;
}

bool get _isComposing {
final composing = _controller.value.composing;
return composing.isValid && !composing.isCollapsed;
}

bool _shouldApplyExternalState({
required bool incomingChanged,
}) {
if (!_focusNode.hasFocus) {
return true;
}
return incomingChanged && !_isComposing;
}

void _handleControllerChange() {
final value = _readValue();
final selection = _controller.selection;
Expand Down Expand Up @@ -160,7 +182,11 @@ class _CodeEditorControlState extends State<CodeEditorControl> {

final value = widget.control.getString("value");
final controllerValue = _readValue();
if (value != null && value != controllerValue) {
final valueChangedInControl = value != _lastControlValue;
_lastControlValue = value;
if (value != null &&
value != controllerValue &&
_shouldApplyExternalState(incomingChanged: valueChangedInControl)) {
_setValue(value);
_value = value;
}
Comment on lines +185 to 192
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

_lastControlValue is updated before the external value is applied. If a control-side value change arrives while the editor is focused and composing, _shouldApplyExternalState blocks applying it, but _lastControlValue still advances—so on subsequent builds valueChangedInControl becomes false and the external update can be dropped permanently (e.g., a programmatic value update during IME composition will never apply even after composition ends/blur). Consider tracking the last applied control value (or keeping a pending external value) and only advancing _lastControlValue after the update is actually applied / when not composing, so suppressed updates can be applied later when it’s safe.

Copilot uses AI. Check for mistakes.
Expand All @@ -170,7 +196,11 @@ class _CodeEditorControlState extends State<CodeEditorControl> {
minOffset: 0,
maxOffset: _controller.text.length,
);
if (explicitSelection != null && explicitSelection != _controller.selection) {
final selectionChangedInControl = explicitSelection != _lastControlSelection;
_lastControlSelection = explicitSelection;
if (explicitSelection != null &&
explicitSelection != _controller.selection &&
_shouldApplyExternalState(incomingChanged: selectionChangedInControl)) {
_controller.selection = explicitSelection;
Comment on lines +200 to 204
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

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

Same pattern for selection: _lastControlSelection is updated even when an incoming selection change is suppressed due to focus/composition. That can prevent a queued control-side selection update from ever being applied later (after composition ends or focus is lost). Consider only updating the "last seen/applied" selection marker when the selection is actually applied, or store a pending selection to apply once _isComposing becomes false.

Suggested change
_lastControlSelection = explicitSelection;
if (explicitSelection != null &&
explicitSelection != _controller.selection &&
_shouldApplyExternalState(incomingChanged: selectionChangedInControl)) {
_controller.selection = explicitSelection;
if (explicitSelection == null) {
// Control explicitly cleared selection; record this immediately.
_lastControlSelection = null;
} else if (explicitSelection != _controller.selection &&
_shouldApplyExternalState(incomingChanged: selectionChangedInControl)) {
_controller.selection = explicitSelection;
_lastControlSelection = explicitSelection;

Copilot uses AI. Check for mistakes.
}

Expand Down
Loading