Repository: vscode
Issue: Cursor appears before decoration on empty lines
Labels: bug,editor-rendering

Type: <b>Bug</b>

Similar to or a regression of issue #40127

Start with decorator-sample example code and replace the body of updateDecorations with:

	function updateDecorations() {
		if (!activeEditor) {
			return;
		}

		const testDecorationType = vscode.window.createTextEditorDecorationType({
			before: {
				contentText: "TEST"
			}
		});

		const testDecorations: vscode.DecorationOptions[] = [];
		const lines = activeEditor.document.getText().split(/\r?\n/);
		for (let i = 0; i < lines.length; i += 1) {
			testDecorations.push({ range: new vscode.Range(i, 0, i, 0) });
		}
		activeEditor.setDecorations(testDecorationType, testDecorations);
	}

Notice that when clicking on a line with no text, the cursor is positioned before the "TEST" decoration instead of after.  Lines that contain text work as expected.

Stepping through VSCode itself, it looks like there's an interaction between renderViewLine() and _readPixelOffset(): 

In src/vs/editor/common/viewLayout/viewLineRenderer.ts:

	export function renderViewLine(input: RenderLineInput, sb: StringBuilder): RenderLineOutput {
		if (input.lineContent.length === 0) {

			if (input.lineDecorations.length > 0) {
				...
				const characterMapping = new CharacterMapping(1, beforeCount + afterCount);
				characterMapping.setColumnInfo(1, beforeCount, 0, 0);
 	 			...

In src/vs/editor/browser/viewParts/lines/viewLine.ts:

	protected _readPixelOffset(...): number {
			if (this._characterMapping.length === 0) {
				// This line has no content
				...
				if (this._containsForeignElements === ForeignElementType.Before) {
					// We have foreign elements before the (empty) line
					return this.getWidth(context);
				}
	
Note that because the CharacterMapping is always created with a length of 1 in renderViewLine when there is a Before decoration, the check in _readPixelOffset always skips over the code to handle the Before case.

To prove that's the case, change renderViewLine to the following and cursor positioning works as expected:

				const characterMapping = new CharacterMapping(0, beforeCount + afterCount);
				//characterMapping.setColumnInfo(1, beforeCount, 0, 0);


VS Code version: Code 1.90.0 (Universal) (89de5a8d4d6205e5b11647eb6a74844ca23d2573, 2024-06-04T19:34:44.157Z)
OS version: Darwin arm64 23.3.0
Modes:

<details>
<summary>System Info</summary>

|Item|Value|
|---|---|
|CPUs|Apple M2 (8 x 2400)|
|GPU Status|2d_canvas: enabled<br>canvas_oop_rasterization: enabled_on<br>direct_rendering_display_compositor: disabled_off_ok<br>gpu_compositing: enabled<br>multiple_raster_threads: enabled_on<br>opengl: enabled_on<br>rasterization: enabled<br>raw_draw: disabled_off_ok<br>skia_graphite: disabled_off<br>video_decode: enabled<br>video_encode: enabled<br>webgl: enabled<br>webgl2: enabled<br>webgpu: enabled|
|Load (avg)|2, 2, 2|
|Memory (System)|16.00GB (0.03GB free)|
|Process Argv|--crash-reporter-id 4c5a5fcb-0d4c-4650-b895-11d0533a6f8a|
|Screen Reader|no|
|VM|0%|
</details>Extensions: none<details>
<summary>A/B Experiments</summary>

```
vsliv368cf:30146710
vspor879:30202332
vspor708:30202333
vspor363:30204092
vscod805:30301674
binariesv615:30325510
vsaa593:30376534
py29gd2263:31024239
c4g48928:30535728
azure-dev_surveyone:30548225
2i9eh265:30646982
962ge761:30959799
pythongtdpath:30769146
welcomedialog:30910333
pythonidxpt:30866567
pythonnoceb:30805159
asynctok:30898717
pythontestfixt:30902429
pythonregdiag2:30936856
pythonmypyd1:30879173
h48ei257:31000450
pythontbext0:30879054
accentitlementsc:30995553
dsvsc016:30899300
dsvsc017:30899301
dsvsc018:30899302
cppperfnew:31000557
dsvsc020:30976470
pythonait:31006305
jchc7451:31067544
showvideot:31016892
chatpanelc:31048052
dsvsc021:30996838
01bff139:31013167
pythoncenvpt:31062603
a69g1124:31058053
dvdeprecation:31068756
pythonprt:31056678
dwnewjupytercf:31046870
newcmakeconfigv2:31071590
26j00206:31048877

```

</details>

<!-- generated by issue reporter -->