Skip to content

Selection not rendered on RTL line when it ends at two empty end-of-line decoration spans #341563

Open
rabeyanoor/vscode
#1

Description

@Mosh-K

Does this issue occur when all extensions are disabled?: No. It needs an extension that adds end-of-line decorations (here GitLens current-line blame), but the bug is in core rendering: RangeUtil.readHorizontalRanges returns null for a DOM shape that the public decorations API produces.

  • VS Code Version: 1.141.1
  • OS Version: Windows 11 Pro 10.0.26200

Steps to Reproduce:

  1. Install GitLens (current-line blame is on by default).
  2. Open a file in a git repo containing a line that has RTL text, e.g. Text: =ThisItem.'שם פרטי'.
  3. Place the cursor on that line so the blame annotation appears at its end.
  4. Select from the middle of the line to its end (e.g. Shift+End), or make a multi-line selection that spans the line.

Expected: the selection background is rendered on that line.
Actual: no selection background on that line. Lines without RTL text are fine.

Cause

The blame decoration renders as two empty spans at the end of the line, one from beforeContentClassName and one from afterContentClassName:

<span class="mtk13" style="unicode-bidi:isolate">=ThisItem.'שם&nbsp;פרטי'</span>
<span class="ced-1-TextEditorDecorationType1-...-3"></span>
<span class="ced-1-TextEditorDecorationType1-...-4"></span>  <!-- ::after has the blame text -->

Because the line contains RTL text, ViewLine skips its LTR fast paths and calls RangeUtil.readHorizontalRanges. The end-of-line column maps to the second empty span, offset 0 (the first isPseudoAfter() part). Then:

  1. rangeUtil.ts#L126-L131 moves the end to the previous span with offset MAX_SAFE_SMALL_INTEGER, but that span is the other empty one.
  2. The empty-span fallback at rangeUtil.ts#L142 only runs when endOffset === 0, so it is skipped.
  3. endElement is null, so the function returns null, and no visible range is produced for the line.

With a single trailing empty span this works, because the step back lands on a span with text.

Possible fix

I tested this fix locally. With it, the selection renders correctly; without it, it doesn't. Unit tests with two trailing empty spans fail before the change and pass after it. I'm happy to open a PR if that's useful.

Diff
+	private static _findPreviousTextNode(domNode: HTMLElement, childIndex: number): ChildNode | null {
+		for (let i = childIndex - 1; i >= 0; i--) {
+			const textNode = domNode.children[i].firstChild;
+			if (textNode) {
+				return textNode;
+			}
+		}
+		return null;
+	}
...
 		if (!startElement || !endElement) {
-			// When having an empty <span> (without any text content), try to move to the previous <span>
-			if (!startElement && startOffset === 0 && startChildIndex > 0) {
-				startElement = domNode.children[startChildIndex - 1].firstChild;
+			// When having empty <span>s (without any text content), try to move to the end of the closest previous <span> with text.
+			// The end of an empty <span> is the same as its beginning, so this also applies when the end was moved above to an empty <span>.
+			if (!startElement && startOffset === 0) {
+				startElement = this._findPreviousTextNode(domNode, startChildIndex);
 				startOffset = Constants.MAX_SAFE_SMALL_INTEGER;
 			}
-			if (!endElement && endOffset === 0 && endChildIndex > 0) {
-				endElement = domNode.children[endChildIndex - 1].firstChild;
+			if (!endElement && (endOffset === 0 || endOffset === Constants.MAX_SAFE_SMALL_INTEGER)) {
+				endElement = this._findPreviousTextNode(domNode, endChildIndex);
 				endOffset = Constants.MAX_SAFE_SMALL_INTEGER;
 			}
 		}

Activity

  1. vs-code-engineering commented on Oct 14, 2026

    @vs-code-engineering
    Contributor

    Thanks for creating this issue! It looks like you may be using an old version of VS Code; the latest stable release is 1.141.2. Please try upgrading to the latest version and checking whether this issue remains.

    Happy Coding!

  2. added a commit that references this issue on Oct 14, 2026
    2ac0dfa
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions