Skip to content

Fix typing over RTL selection requiring two keypresses - #331077

Open
jibin jose (jibin7jose) wants to merge 1 commit into
microsoft:mainfrom
jibin7jose:fix-rtl-selection-keypress
Open

Fix typing over RTL selection requiring two keypresses#331077
jibin jose (jibin7jose) wants to merge 1 commit into
microsoft:mainfrom
jibin7jose:fix-rtl-selection-keypress

Conversation

@jibin7jose

Copy link
Copy Markdown

Fixes microsoft/monaco-editor#5448

Description of Proposed Changes

This PR resolves an input bug in the legacy <textarea> input pipeline (the path used when editContext is disabled, such as natively in Safari).

  • The Problem: When selecting text from right to left (RTL selection) and typing a key, the selected text is not replaced on the first keypress. A second keypress is required.
  • The Root Cause: TextAreaState expects standard, normalized selection bounds (selectionStart <= selectionEnd). However, the getSelectionStart() and getSelectionEnd() methods in TextAreaWrapper were flipping these values for backward/RTL selections. This caused selectionStart > selectionEnd to be written and read, which violated native browser behavior (browsers automatically clamp/collapse native selections where start > end) and resulted in a state diff mismatch inside deduceInput (computing replacePrevCharCnt = 1 instead of 0).
  • The Fix: Removed the direction-based flipping logic in TextAreaWrapper.getSelectionStart and getSelectionEnd to always return standard LTR range bounds, aligning with the expected format in TextAreaState and maintaining selection synchronization.

How to Test

  1. Open the Monaco Editor playground (or test runner) with editContext: false configured (or open in Safari with default options).
  2. Place the cursor at the end of a line (e.g. after the semicolon in const U = 19.05;).
  3. Press Shift+ArrowLeft once to select the ; character backwards.
  4. Press the a key once.
  5. Expected Behavior: The selection ; is immediately replaced with a on the first keypress, resulting in const U = 19.05a.

Copilot AI balanced review requested due to automatic review settings August 16, 2026 13:50

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Note

Copilot was unable to run its full agentic suite in this review.

Updates the TextAreaWrapper selection accessors to return the textarea’s raw selectionStart/selectionEnd values instead of swapping based on selectionDirection.

Changes:

  • Simplified getSelectionStart() to always return this._actual.selectionStart.
  • Simplified getSelectionEnd() to always return this._actual.selectionEnd.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.


public getSelectionStart(): number {
return this._actual.selectionDirection === 'backward' ? this._actual.selectionEnd : this._actual.selectionStart;
return this._actual.selectionStart;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This change is the correct fix for this input pipeline.

TextAreaState and the input diffing engine (deduceInput) assume standard, normalized selection boundaries (selectionStart <= selectionEnd). Standard browsers also do not support setting a selection range where selectionStart > selectionEnd (doing so collapses the selection).

Swapping selectionStart and selectionEnd for backward/RTL selections violates these assumptions, leading to a state mismatch where the native selection is collapsed and replacePrevCharCnt is incorrectly computed as 1 instead of 0 on the first keypress.

Since the hidden textarea is only used for IME/input bridging and not direct user selection interaction, returning normalized boundaries here is safe and correctly aligns the native state with TextAreaState expectations, solving the double-keypress bug without breaking caret logic.


public getSelectionEnd(): number {
return this._actual.selectionDirection === 'backward' ? this._actual.selectionStart : this._actual.selectionEnd;
return this._actual.selectionEnd;

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

This change is the correct fix for this input pipeline.

TextAreaState and the input diffing engine (deduceInput) assume standard, normalized selection boundaries (selectionStart <= selectionEnd). Standard browsers also do not support setting a selection range where selectionStart > selectionEnd (doing so collapses the selection).

Swapping selectionStart and selectionEnd for backward/RTL selections violates these assumptions, leading to a state mismatch where the native selection is collapsed and replacePrevCharCnt is incorrectly computed as 1 instead of 0 on the first keypress.

Since the hidden textarea is only used for IME/input bridging and not direct user selection interaction, returning normalized boundaries here is safe and correctly aligns the native state with TextAreaState expectations, solving the double-keypress bug without breaking caret logic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] Typing over a right-to-left selection requires two keypresses on the textarea input path (Safari)

3 participants