Repository: microsoft/vscode (TypeScript)
Date: 2022-05-02 14:08:08+00:00
Task type: Bug Fix

Issue:
Tab object `isPinned` not correctly updated after pinning tab
Testing #145585

1. Pin a tab
2. `onDidChangeTab` event fires but tab object has `isPinned` set to false, also `isPreview` is set to true


Issue discussion:
@lramos15 😞  this would have been a wonderful case for integration test. I think we really need to cover tabs API with an extensive set of tests given it is so easy to test.

Here is a test that fails before your change and succeeds after:

```ts
test('Tabs - verify pinned state', async () => {

	const [docA] = await Promise.all([
		workspace.openTextDocument(await createRandomFile())
	]);

	// Function to acquire the active tab within the active group
	const getActiveTabInActiveGroup = () => {
		const activeGroup = window.tabGroups.groups.filter(group => group.isActive)[0];
		return activeGroup?.activeTab;
	};

	await window.showTextDocument(docA, { viewColumn: ViewColumn.One, preview: false });
	let activeTab = getActiveTabInActiveGroup();
	assert.strictEqual(activeTab?.isPinned, false);

	let onDidChangeTab = asPromise(window.tabGroups.onDidChangeTab);

	await commands.executeCommand('workbench.action.pinEditor');
	await onDidChangeTab;

	assert.strictEqual(activeTab.isPinned, true);
});
```
@bpasero I agree that more integration tests are needed and I will continue to work on improving the testing coverage. Even a simple unit test would've caught this one since the exthost side is wrong. Thanks for adding the test!
I wanna take chance to explain why we shouldn't have such integration tests. It reads all nice but `await commands.executeCommand('workbench.action.pinEditor');` is pinning _some_ editor. There is no guarantee that the active editor is still the one you think it is - it's two processes and loads of extension code, making this a recipe for flaky tests. 

So, either the command accepts some form of editor identification and returns a status if it could successfully pin that editor or we have dedicated API (which I assume we wanna have near term anyways) that pins an editor. 
Yeah agreed, I commented them out again and think https://github.com/microsoft/vscode/issues/145846 is the actual work item for this task where that should be taken into consideration.

If tabs API had the means to change the properties it provides (like active, pinned, preview), it would be even easier to write a test for it but until then the commands need to be used in a way that we know for certain the editor is changed that we expect.
I know this was closed but I get consistently incorrect results in 

```vscode.window.tabGroups.onDidChangeTabs(async ({added, changed, removed}) => {...check in here...})```

when pinning and unpinning tabs.  Pinning a tab seems to work correctly the first time (the `changed` tab's `isPinned` is reported as `true`, but thereafter `isPinned` is always reported as `false` when a tab is pinned - `onDidChangeTabs` does fire each time though.

Moreover, if I much later check `vscode.window.tabGroups.activeTabGroup` via a different command, the `isPinned` status for these recently pinned tabs is reported incorrectly even though the have the pin icon as they should.

This occurs for all situations:

1. Pin a focused tab.  (Via context menu/Pin)
2. Unpin a focused tab.
3. Pin a non-focused tab.
4. Unpin a non-focused tab.
Thanks @ArturoDent I will investigate this
1. Have multiple tabs open
2. Add

    ```
	vscode.window.tabGroups.onDidChangeTabs(evt => {
		vscode.window.showInformationMessage(evt.changed.map(t => `${t.label} ${t.isPinned}`).join(', '));
	});
    ```

3. Pin anything other than the first (left-most) tab
4. See the message and notice that its `isPinned` is `false` 🐛 

@bpasero If you have 3 editors open and pin the last editor then the event fires to say it's sticky and then `group.isSticky(2)` returns false when it should be true.
Looks like I was passing the old index rather the new one since pinning also moves the tab
@lramos15 good catch, don't we have the same bug also for `unstick`? I will push a fix to May and a test, I think it was always sending the wrong index...

Not sure it is a candidate...
If you want to backport, https://github.com/microsoft/vscode/commit/63a16e1290298b6bc82ea98aca798a6ab82ec159 would be the relevant change.

Solution patch:
diff --git a/src/vs/workbench/common/editor/editorGroupModel.ts b/src/vs/workbench/common/editor/editorGroupModel.ts
--- a/src/vs/workbench/common/editor/editorGroupModel.ts
+++ b/src/vs/workbench/common/editor/editorGroupModel.ts
@@ -567,7 +567,7 @@ export class EditorGroupModel extends Disposable {
 			kind: GroupModelChangeKind.EDITOR_MOVE,
 			editor,
 			oldEditorIndex: index,
-			editorIndex: toIndex,
+			editorIndex: toIndex
 		};
 		this._onDidModelChange.fire(event);
 
@@ -735,7 +735,8 @@ export class EditorGroupModel extends Disposable {
 		this.pin(editor);
 
 		// Move editor to be the last sticky editor
-		this.moveEditor(editor, this.sticky + 1);
+		const newEditorIndex = this.sticky + 1;
+		this.moveEditor(editor, newEditorIndex);
 
 		// Adjust sticky index
 		this.sticky++;
@@ -744,7 +745,7 @@ export class EditorGroupModel extends Disposable {
 		const event: IGroupEditorChangeEvent = {
 			kind: GroupModelChangeKind.EDITOR_STICKY,
 			editor,
-			editorIndex: this.sticky
+			editorIndex: newEditorIndex
 		};
 		this._onDidModelChange.fire(event);
 	}
@@ -768,7 +769,8 @@ export class EditorGroupModel extends Disposable {
 		}
 
 		// Move editor to be the first non-sticky editor
-		this.moveEditor(editor, this.sticky);
+		const newEditorIndex = this.sticky;
+		this.moveEditor(editor, newEditorIndex);
 
 		// Adjust sticky index
 		this.sticky--;
@@ -777,7 +779,7 @@ export class EditorGroupModel extends Disposable {
 		const event: IGroupEditorChangeEvent = {
 			kind: GroupModelChangeKind.EDITOR_STICKY,
 			editor,
-			editorIndex
+			editorIndex: newEditorIndex
 		};
 		this._onDidModelChange.fire(event);
 	}


Test patch:
diff --git a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts
--- a/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts
+++ b/src/vs/workbench/test/browser/parts/editor/editorGroupModel.test.ts
@@ -2232,6 +2232,40 @@ suite('EditorGroupModel', () => {
 		assert.strictEqual(group.indexOf(input4), 2);
 	});
 
+	test('Sticky/Unsticky Editors sends correct editor index', function () {
+		const group = createEditorGroupModel();
+
+		const input1 = input();
+		const input2 = input();
+		const input3 = input();
+
+		group.openEditor(input1, { pinned: true, active: true });
+		group.openEditor(input2, { pinned: true, active: true });
+		group.openEditor(input3, { pinned: false, active: true });
+
+		assert.strictEqual(group.stickyCount, 0);
+
+		const events = groupListener(group);
+
+		group.stick(input3);
+
+		assert.strictEqual(events.sticky[0].editorIndex, 0);
+		assert.strictEqual(group.isSticky(input3), true);
+		assert.strictEqual(group.stickyCount, 1);
+
+		group.stick(input2);
+
+		assert.strictEqual(events.sticky[1].editorIndex, 1);
+		assert.strictEqual(group.isSticky(input2), true);
+		assert.strictEqual(group.stickyCount, 2);
+
+		group.unstick(input3);
+		assert.strictEqual(events.unsticky[0].editorIndex, 1);
+		assert.strictEqual(group.isSticky(input3), false);
+		assert.strictEqual(group.isSticky(input2), true);
+		assert.strictEqual(group.stickyCount, 1);
+	});
+
 	test('onDidMoveEditor Event', () => {
 		const group1 = createEditorGroupModel();
 		const group2 = createEditorGroupModel();