Repository: microsoft/vscode (TypeScript) — Bug Fix

Issue:
Text gets automatically selected on last active line, in non-focussed editor in Split Editor
- VSCode Version: 1.51.1
- OS Version: Windows 10 20H2, Build 19042.630

When using the Split Editor feature with the same file open on both sides, text that is entered on the line that was last active on the editor that doesn't have focus, gets automatically selected.

Steps to Reproduce:

1. Start with just one file visible in the only visible editor. See the screenshot below. Notice that the text cursor is on Line 4.
![image](https://user-images.githubusercontent.com/3789849/100623192-b80d2400-3319-11eb-8fce-d1332e19eb51.png)

2. Click the 'Split Editor Right' button, to open that same file on the right hand side. See the screenshot below. Notice that the text cursor in the editor on the right is also on Line 4, and that the editor on the left side (the non-focussed one), also still has Line 4 highlighted.
![image](https://user-images.githubusercontent.com/3789849/100623394-028ea080-331a-11eb-88c0-7e8e0150219b.png)

3. Type a character. In the example screenshot below, I've typed the letter 'e'. Notice what happens in the left (non-focussed) editor. There, the letter 'e' gets selected, and of course every instance of the letter 'e' in the entire document is highlighted as well. You can also see that reflected in the scrollbar highlighting. On the right side (the focussed editor), nothing is selected and therefore nothing is highlighted.
![image](https://user-images.githubusercontent.com/3789849/100624769-b2184280-331b-11eb-9ea6-b72e9ad0481c.png)

4. Delete that character 'e' that was typed on Line 4, so that nothing is highlighted anywhere anymore. Now put the cursor on Line 8 and type any character. See the screenshot below, where I've typed the letter 'a'. In this case, nothing is highlighted on either side, because Line 8 was not the active line at the time when Split Editor was enabled.
![image](https://user-images.githubusercontent.com/3789849/100624143-f6571300-331a-11eb-9cab-a2edca6d193c.png)

In my view, what happens in Step 3 is not expected and not desirable. Just because that was where the cursor was when the Split Editor featured was enabled, why should any text entered on that line get automatically selected. Also, in Step 2, Line 4 on the non-focussed side should not remain highlighted.


Discussion:

Resolving patch:
diff --git a/src/vs/editor/common/core/range.ts b/src/vs/editor/common/core/range.ts
--- a/src/vs/editor/common/core/range.ts
+++ b/src/vs/editor/common/core/range.ts
@@ -337,17 +337,31 @@ export class Range {
 	}
 
 	/**
-	 * Moves the range by the given amount of lines.
+	 * Create a new empty range using this range's start position.
 	 */
-	public delta(lineCount: number): Range {
-		return new Range(this.startLineNumber + lineCount, this.startColumn, this.endLineNumber + lineCount, this.endColumn);
+	public static collapseToStart(range: IRange): Range {
+		return new Range(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn);
 	}
 
 	/**
-	 * Create a new empty range using this range's start position.
+	 * Create a new empty range using this range's end position.
 	 */
-	public static collapseToStart(range: IRange): Range {
-		return new Range(range.startLineNumber, range.startColumn, range.startLineNumber, range.startColumn);
+	public collapseToEnd(): Range {
+		return Range.collapseToEnd(this);
+	}
+
+	/**
+	 * Create a new empty range using this range's end position.
+	 */
+	public static collapseToEnd(range: IRange): Range {
+		return new Range(range.endLineNumber, range.endColumn, range.endLineNumber, range.endColumn);
+	}
+
+	/**
+	 * Moves the range by the given amount of lines.
+	 */
+	public delta(lineCount: number): Range {
+		return new Range(this.startLineNumber + lineCount, this.startColumn, this.endLineNumber + lineCount, this.endColumn);
 	}
 
 	// ---
diff --git a/src/vs/editor/common/cursor/oneCursor.ts b/src/vs/editor/common/cursor/oneCursor.ts
--- a/src/vs/editor/common/cursor/oneCursor.ts
+++ b/src/vs/editor/common/cursor/oneCursor.ts
@@ -64,6 +64,12 @@ export class Cursor {
 
 	public readSelectionFromMarkers(context: CursorContext): Selection {
 		const range = context.model._getTrackedRange(this._selTrackedRange!)!;
+
+		if (this.modelState.selection.isEmpty() && !range.isEmpty()) {
+			// Avoid selecting text when recovering from markers
+			return Selection.fromRange(range.collapseToEnd(), this.modelState.selection.getDirection());
+		}
+
 		return Selection.fromRange(range, this.modelState.selection.getDirection());
 	}
 
diff --git a/src/vs/monaco.d.ts b/src/vs/monaco.d.ts
--- a/src/vs/monaco.d.ts
+++ b/src/vs/monaco.d.ts
@@ -705,14 +705,22 @@ declare namespace monaco {
 		 * Create a new empty range using this range's start position.
 		 */
 		collapseToStart(): Range;
-		/**
-		 * Moves the range by the given amount of lines.
-		 */
-		delta(lineCount: number): Range;
 		/**
 		 * Create a new empty range using this range's start position.
 		 */
 		static collapseToStart(range: IRange): Range;
+		/**
+		 * Create a new empty range using this range's end position.
+		 */
+		collapseToEnd(): Range;
+		/**
+		 * Create a new empty range using this range's end position.
+		 */
+		static collapseToEnd(range: IRange): Range;
+		/**
+		 * Moves the range by the given amount of lines.
+		 */
+		delta(lineCount: number): Range;
 		static fromPositions(start: IPosition, end?: IPosition): Range;
 		/**
 		 * Create a `Range` from an `IRange`.


Test patch:
diff --git a/src/vs/editor/test/browser/commands/sideEditing.test.ts b/src/vs/editor/test/browser/commands/sideEditing.test.ts
--- a/src/vs/editor/test/browser/commands/sideEditing.test.ts
+++ b/src/vs/editor/test/browser/commands/sideEditing.test.ts
@@ -46,7 +46,7 @@ suite('Editor Side Editing - collapsed selection', () => {
 				'third line',
 				'fourth'
 			],
-			[new Selection(1, 1, 1, 11)]
+			[new Selection(1, 11, 1, 11)]
 		);
 	});
 
@@ -233,7 +233,7 @@ suite('SideEditing', () => {
 					new Range(1, 4, 1, 4),
 					new Range(1, 4, 1, 4), 'xx',
 					[
-						[new Selection(1, 4, 1, 6), new Selection(1, 4, 1, 6)],
+						[new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
 						[new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
 					]
 				);
@@ -704,7 +704,7 @@ suite('SideEditing', () => {
 					new Range(1, 4, 1, 4),
 					new Range(1, 2, 1, 4), 'cccc',
 					[
-						[new Selection(1, 4, 1, 6), new Selection(1, 4, 1, 6)],
+						[new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
 						[new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6)],
 					]
 				);
diff --git a/src/vs/editor/test/browser/controller/cursor.test.ts b/src/vs/editor/test/browser/controller/cursor.test.ts
--- a/src/vs/editor/test/browser/controller/cursor.test.ts
+++ b/src/vs/editor/test/browser/controller/cursor.test.ts
@@ -1791,7 +1791,7 @@ suite('Editor Controller', () => {
 
 			CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
 			assert.strictEqual(model.getLineContent(1), 'Hello world ');
-			assertCursor(viewModel, new Selection(1, 12, 1, 13));
+			assertCursor(viewModel, new Selection(1, 13, 1, 13));
 
 			CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
 			assert.strictEqual(model.getLineContent(1), 'Hello world');
@@ -6128,6 +6128,30 @@ suite('Editor Controller', () => {
 			assertCursor(viewModel, new Selection(3, 5, 2, 1));
 		});
 	});
+
+	test('issue #111513: Text gets automatically selected when typing at the same location in another editor', () => {
+		const model = createTextModel(
+			[
+				'just',
+				'',
+				'some text',
+			].join('\n')
+		);
+
+		withTestCodeEditor(model, {}, (editor1, viewModel1) => {
+			editor1.setSelections([
+				new Selection(2, 1, 2, 1)
+			]);
+			withTestCodeEditor(model, {}, (editor2, viewModel2) => {
+				editor2.setSelections([
+					new Selection(2, 1, 2, 1)
+				]);
+				viewModel2.type('e', 'keyboard');
+				assertCursor(viewModel2, new Position(2, 2));
+				assertCursor(viewModel1, new Position(2, 2));
+			});
+		});
+	});
 });
 
 suite('Undo stops', () => {