Skip to content

Conversation

@tusharonly
Copy link
Contributor

PR Description

Fixed cursor jumping to end issue in URL text field. When editing a character in the middle of the URL, the cursor would automatically move to the end, making it difficult to edit URLs.

Root Cause: The didUpdateWidget method in EnvironmentTriggerField was recreating the TextEditingController on every text change, always positioning the cursor at the end.

Solution: Modified the controller update logic to:

  • Preserve the current cursor position when text is updated from user input
  • Only reset cursor to end when switching between different requests (keyId changes)
  • Ensure cursor position stays within valid bounds using .clamp(0, newText.length)
  • Avoid unnecessary controller updates when text hasn't actually changed

Related Issues

Checklist

  • I have gone through the contributing guide

  • I have updated my branch and synced it with project main branch before making this PR

  • I am using the latest Flutter stable branch (run flutter upgrade and verify)

  • I have run the tests (flutter test) and all tests are passing

Added/updated tests?

We encourage you to add relevant test cases.

  • No, and this is why: This is a UI interaction fix that addresses cursor positioning behavior. The existing tests continue to pass and validate the text field functionality. Additional widget tests for cursor position preservation could be added in a future enhancement.

OS on which you have developed and tested the feature?

  • Windows

  • macOS

  • Linux

…ing initial value and fix controller handling on key change
@animator animator requested a review from Copilot December 4, 2025 17:42
Copilot finished reviewing on behalf of animator December 4, 2025 17:44
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR fixes a cursor positioning bug where editing text in the middle of a URL field would cause the cursor to jump to the end. The fix preserves the cursor position during text updates while still resetting it when switching between different requests.

Key Changes:

  • Modified didUpdateWidget to distinguish between keyId changes (new request) and value changes (same request being edited)
  • Added cursor position preservation logic that clamps the cursor offset within valid bounds
  • Updated indentation in the ExtendedTextField widget

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

_focusNode.unfocus();
},
readOnly: widget.readOnly,
obscureText: widget.obscureText
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Missing comma after the obscureText parameter. This will cause a syntax error and prevent the code from compiling.

Add a comma after widget.obscureText:

obscureText: widget.obscureText,
Suggested change
obscureText: widget.obscureText
obscureText: widget.obscureText,

Copilot uses AI. Check for mistakes.
Comment on lines +70 to 76
if (oldWidget.keyId != widget.keyId) {
// Key changed - create new controller with cursor at end
controller = widget.controller ??
TextEditingController.fromValue(TextEditingValue(
text: widget.initialValue!,
selection: TextSelection.collapsed(
offset: widget.initialValue!.length)));
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

Creating a new controller without disposing the old one will cause a memory leak. When the keyId changes, the old controller should be disposed before assigning a new one.

Consider adding:

if (oldWidget.keyId != widget.keyId) {
  // Key changed - dispose old controller and create new one
  if (widget.controller == null) {
    controller.dispose();
  }
  controller = widget.controller ??
      TextEditingController.fromValue(TextEditingValue(
          text: widget.initialValue!,
          selection: TextSelection.collapsed(
              offset: widget.initialValue!.length)));
}

Copilot uses AI. Check for mistakes.
@animator animator requested a review from Copilot December 4, 2025 19:06
Copilot finished reviewing on behalf of animator December 4, 2025 19:08
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 1 out of 1 changed files in this pull request and generated 2 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +87 to +91
final newOffset =
currentSelection.baseOffset.clamp(0, newText.length);
controller.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: newOffset),
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The cursor position preservation logic doesn't account for text selection ranges (when text is highlighted). The code only preserves baseOffset but ignores extentOffset, which means any text selection will be lost and collapsed to a cursor position.

Consider preserving the full selection when possible:

final currentSelection = controller.selection;
final newText = widget.initialValue ?? '';

if (controller.text != newText) {
  // Preserve selection range, ensuring it's within bounds
  final newBaseOffset = currentSelection.baseOffset.clamp(0, newText.length);
  final newExtentOffset = currentSelection.extentOffset.clamp(0, newText.length);
  
  controller.value = TextEditingValue(
    text: newText,
    selection: TextSelection(
      baseOffset: newBaseOffset,
      extentOffset: newExtentOffset,
    ),
  );
}
Suggested change
final newOffset =
currentSelection.baseOffset.clamp(0, newText.length);
controller.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: newOffset),
final newBaseOffset = currentSelection.baseOffset.clamp(0, newText.length);
final newExtentOffset = currentSelection.extentOffset.clamp(0, newText.length);
controller.value = TextEditingValue(
text: newText,
selection: TextSelection(
baseOffset: newBaseOffset,
extentOffset: newExtentOffset,
),

Copilot uses AI. Check for mistakes.
Comment on lines +77 to +94
} else if (oldWidget.initialValue != widget.initialValue) {
// Initial value changed but key is same
// Preserve cursor position if text is being updated
if (widget.controller == null) {
final currentSelection = controller.selection;
final newText = widget.initialValue ?? '';

// Only update if the text actually differs from controller's current text
if (controller.text != newText) {
// Preserve cursor position, ensuring it's within bounds
final newOffset =
currentSelection.baseOffset.clamp(0, newText.length);
controller.value = TextEditingValue(
text: newText,
selection: TextSelection.collapsed(offset: newOffset),
);
}
}
Copy link

Copilot AI Dec 4, 2025

Choose a reason for hiding this comment

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

The cursor position preservation logic introduced in this PR lacks test coverage. While there are existing tests that verify text updates, there are no tests that specifically validate that the cursor position is preserved when text is updated (the main fix of this PR).

Consider adding a test case like:

testWidgets('Testing EnvironmentTriggerField preserves cursor position on text update',
    (WidgetTester tester) async {
  final fieldKey = GlobalKey<EnvironmentTriggerFieldState>();
  
  await tester.pumpWidget(
    Portal(
      child: MaterialApp(
        home: Scaffold(
          body: EnvironmentTriggerField(
            key: fieldKey,
            keyId: 'testKey',
            initialValue: 'hello world',
          ),
        ),
      ),
    ),
  );
  
  // Set cursor to position 5 (middle of text)
  fieldKey.currentState!.controller.selection = 
      TextSelection.collapsed(offset: 5);
  
  // Update the text value
  await tester.pumpWidget(
    Portal(
      child: MaterialApp(
        home: Scaffold(
          body: EnvironmentTriggerField(
            key: fieldKey,
            keyId: 'testKey',
            initialValue: 'hello world!',
          ),
        ),
      ),
    ),
  );
  
  // Verify cursor is still at position 5
  expect(fieldKey.currentState!.controller.selection.baseOffset, 5);
});

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Typing or Removing any character in between URL textfield moves the cursor to the end unexpectedly.

2 participants