Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/vs/sessions/SINGLE_PANE_SCENARIOS.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ Let **E** = editor content visible, **D** = detail panel visible. The pane suppo
| **Editor only** | ✅ | ❌ | Detail toggled off; editor content fills the pane; tab bar across the top. |
| **Side pane closed** | ❌ | ❌ | The whole third pane is closed (chat-only). Reached via **Toggle Side Panel** or when the last editor tab closes; never via the detail toggle. **Closing the whole side pane does NOT close editors** — only a *Detail-only* collapse (editor hidden while the detail stays open) closes them; when both parts hide the editors are left intact so they return when the side pane is reopened. |

Only **Existing Sessions** share a persisted Editor/Details visibility profile. A New Session does not apply or capture that profile; on entry it hides Editor once only when the restored editor set contains no input other than Empty Files. Submit seeds the Existing profile. The active editor selects the detail content: every diff editor selects Changes and every file editor selects Files.
Only **Existing Sessions** share a persisted Editor/Details visibility profile. A New Session does not apply or capture that profile; on entry it hides Editor once only when the restored editor set contains no input other than the managed Changes and Empty Files inputs. Submit seeds the Existing profile. The active editor selects the detail content: every diff editor selects Changes and every file editor selects Files.
Comment thread
sandy081 marked this conversation as resolved.

**Size distribution when opening the side pane.** Opening the side pane from *closed* (e.g. clicking
**Changes** while the chat is full-width) reveals the editor with `Sizing.Distribute`. The grid uses
Expand Down Expand Up @@ -137,7 +137,7 @@ layout keeps it; single-pane's own **Show Editor** action is its counterpart to
## 4. Tabs

- **Changes** — a custom `SessionChangesEditor` (Branch Changes dropdown + diff stats + embedded
multi-diff). Pinned first, present for every session with a workspace.
multi-diff). Pinned first, present for every session with a workspace, including New Session drafts.
- **File** — the empty File tab (`EmptyFileEditorInput`) as a landing tab, plus real file editors the
user opens. Opened **pinned, inactive, preserve-focus** so it never steals focus from the chat.
- **Browser** — the integrated browser (`BrowserEditorInput`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { CHANGES_VIEW_CONTAINER_ID, CHANGES_VIEW_ID, SESSIONS_CHANGES_OPEN_SINGL
import { ChangesViewPane, SinglePaneChangesViewPane, ChangesViewPaneContainer } from './changesView.js';
import { SessionChangesEditor } from './sessionChangesEditor.js';
import { SessionChangesEditorInput, SessionChangesEditorSerializer } from './sessionChangesEditorInput.js';
import { IsPhoneLayoutContext, SessionHasWorkspaceContext, SessionIsCreatedContext } from '../../../common/contextkeys.js';
import { IsPhoneLayoutContext, SessionHasWorkspaceContext } from '../../../common/contextkeys.js';
import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js';
import { ISessionChangesService, SessionChangesService } from './sessionChangesService.js';
import './changesActions.js';
Expand Down Expand Up @@ -98,7 +98,7 @@ const changesViewContainer = viewContainersRegistry.registerViewContainer({

const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);

export const changesViewWhen = ContextKeyExpr.and(IsPhoneLayoutContext.negate(), SessionHasWorkspaceContext, SessionIsCreatedContext);
export const changesViewWhen = ContextKeyExpr.and(IsPhoneLayoutContext.negate(), SessionHasWorkspaceContext);

/**
* Registers the Changes view with the layout-appropriate pane class: the single-pane
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ suite('Changes View Actions', () => {
({ changesViewWhen } = await import('../../browser/changes.contribution.js'));
});

test('Changes view is hidden until the session is created', () => {
test('Changes view is available for new and created workspace sessions', () => {
assert.ok(changesViewWhen);
const context = new Context(1, null);
context.setValue(IsPhoneLayoutContext.key, false);
Expand All @@ -52,7 +52,7 @@ suite('Changes View Actions', () => {
whileNew,
afterCreation: changesViewWhen.evaluate(context),
}, {
whileNew: false,
whileNew: true,
afterCreation: true,
});
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,8 @@ const FILES_TAB_OPTIONS: IEditorOptions = { pinned: true, inactive: true, preser

/**
* What the active session wants from its managed docked tabs.
* - `changesSessionResource`: set for a created workspace session (the Changes multi-diff tab). `undefined` otherwise.
* - `wantsChangesTab`: `true` for a created workspace session.
* - `changesSessionResource`: set for any workspace session (the Changes multi-diff tab). `undefined` otherwise.
* - `wantsChangesTab`: `true` for any workspace session.
* - `wantsFilesTab`: `true` for any workspace, non-quick-chat session (the empty Files placeholder tab).
*/
export interface IManagedTabsTarget {
Expand Down Expand Up @@ -147,14 +147,17 @@ export class SinglePaneDockedTabsCoordinator extends Disposable {

// [Ambient trigger] Session switch / created transition, kind-agnostic (fires for New,
// Existing, and Quick Chat alike — a quick chat's target wants neither tab, so this
// reconciles any stray managed tabs away). The New/Existing-specific "ensure the
// Changes tab" nuances are supplied by those strategies via `queueReconcile`.
// reconciles any stray managed tabs away).
let previousChangesSessionResource: URI | undefined;
this._register(autorun(reader => {
const target = this._readTarget(reader);
const ensureChanges = !!target.changesSessionResource
&& (!previousChangesSessionResource || !isEqual(previousChangesSessionResource, target.changesSessionResource));
previousChangesSessionResource = target.changesSessionResource;
if (!target.wantsChangesTab) {
this._filesTabDismissed = false;
}
this.queueReconcile(target, { openDefaultsIfEmpty: true });
this.queueReconcile(target, { openDefaultsIfEmpty: true, ensureChanges });
}));

// [Ambient trigger] The user opened the side pane.
Expand Down Expand Up @@ -302,8 +305,7 @@ export class SinglePaneDockedTabsCoordinator extends Disposable {
if (!session || isQuickChat || !workspace) {
return { changesSessionResource: undefined, workspace: undefined, wantsChangesTab: false, wantsFilesTab: false };
}
const isCreated = read(session.isCreated);
return { changesSessionResource: isCreated ? session.resource : undefined, workspace, wantsChangesTab: isCreated, wantsFilesTab: true };
return { changesSessionResource: session.resource, workspace, wantsChangesTab: true, wantsFilesTab: true };
}

// --- Reconcile --------------------------------------------------------
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ export class SinglePaneNewSessionStrategy extends SinglePaneLayoutStrategy {
if (editors.length === 0) {
return;
}
if (!editors.every((editor) => editor instanceof EmptyFileEditorInput)) {
if (!editors.every(editor => editor instanceof EmptyFileEditorInput || isChangesEditorInput(editor, this._sessionChangesService))) {
Comment thread
sandy081 marked this conversation as resolved.
return;
}

Expand Down Expand Up @@ -165,10 +165,7 @@ export class SinglePaneNewSessionStrategy extends SinglePaneLayoutStrategy {
return;
}
this._pendingSidePaneOpenHideSessionKey = undefined;
if (
editors.length !== 1 ||
!(editors[0] instanceof EmptyFileEditorInput)
) {
if (!editors.every(editor => editor instanceof EmptyFileEditorInput || isChangesEditorInput(editor, this._sessionChangesService))) {
return;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2762,7 +2762,7 @@ suite('LayoutController (desktop)', () => {
});
});

test('[managed tabs / Scenario 9] shows only Files for a new-session view', async () => {
test('[managed tabs / Scenario 9] shows Changes and Files for a new-session view', async () => {
createSinglePaneController({ activateAux: true });
await settle();

Expand All @@ -2774,13 +2774,13 @@ suite('LayoutController (desktop)', () => {
hasFilesTab: hasFilesTab(),
changesTabMissing: harness.contextKeyService.getContextKeyValue(SinglePaneChangesTabMissingContext.key),
}, {
hasChangesTab: false,
hasChangesTab: true,
hasFilesTab: true,
changesTabMissing: false,
});
});

test('[managed tabs / new session] keeps Changes unavailable after a delayed different-folder restore', async () => {
test('[managed tabs / new session] restores Changes after a delayed different-folder restore', async () => {
const controller = createSinglePaneController({ activateAux: true });
await settle();

Expand All @@ -2794,7 +2794,7 @@ suite('LayoutController (desktop)', () => {

harness.activeSessionObs.set(makeSession(URI.parse('session:new'), { status: SessionStatus.Untitled, isCreated: false }), undefined);
await settle();
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: false, hasFilesTab: true });
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: true });

// A different default folder delays this restore until after the draft reconcile.
const filesTab = harness.activeGroupEditors.find(editor => editor instanceof EmptyFileEditorInput);
Expand All @@ -2806,7 +2806,7 @@ suite('LayoutController (desktop)', () => {
});
await settle();

assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: false, hasFilesTab: true });
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: true });
});

test('[managed tabs / submit] activates Changes only after a submitted session reports changes', async () => {
Expand All @@ -2816,7 +2816,7 @@ suite('LayoutController (desktop)', () => {
const session = makeSession(URI.parse('session:new'), { status: SessionStatus.Untitled, isCreated: false });
harness.activeSessionObs.set(session, undefined);
await settle();
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: false, hasFilesTab: true });
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: true });

// Submit from the Files tab: visibility and the active tab stay unchanged.
harness.activeEditorInput = harness.activeGroupEditors.find(e => e instanceof EmptyFileEditorInput);
Expand All @@ -2840,11 +2840,11 @@ suite('LayoutController (desktop)', () => {
createSinglePaneController({ activateAux: true });
await settle();

// New-session draft active: only Files is present.
// New-session draft active: Changes and Files are present.
const draft = makeSession(URI.parse('session:draft'), { status: SessionStatus.Untitled, isCreated: false });
harness.activeSessionObs.set(draft, undefined);
await settle();
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: false, hasFilesTab: true });
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: true });

// The provider commits the draft by replacing it with a new created resource.
const committedResource = URI.parse('session:committed');
Expand Down Expand Up @@ -2872,11 +2872,11 @@ suite('LayoutController (desktop)', () => {
createSinglePaneController({ activateAux: true });
await settle();

// Session A is a new-session draft with only Files.
// Session A is a new-session draft with Changes and Files.
const sessionA = makeSession(URI.parse('session:a'), { status: SessionStatus.Untitled, isCreated: false });
harness.activeSessionObs.set(sessionA, undefined);
await settle();
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: false, hasFilesTab: true });
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: true });

// Pause the very next Changes open so A's submit reconcile stalls mid-open.
let releaseChangesOpen!: () => void;
Expand Down Expand Up @@ -3065,7 +3065,7 @@ suite('LayoutController (desktop)', () => {
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: false });
});

test('[managed tabs / new session] re-opens Files when a working-set apply empties the group during the switch', async () => {
test('[managed tabs / new session] re-opens managed tabs when a working-set apply empties the group during the switch', async () => {
const controller = createSinglePaneController({ activateAux: true });
await settle();

Expand All @@ -3084,11 +3084,11 @@ suite('LayoutController (desktop)', () => {
});
await settle();

// Only Files is restored for the uncreated session.
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: false, hasFilesTab: true });
// Changes and Files are restored for the uncreated session.
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: true });
});

test('[managed tabs / new session] re-opens Files on restore-end even if no editor-change fires during the restore', async () => {
test('[managed tabs / new session] re-opens managed tabs on restore-end even if no editor-change fires during the restore', async () => {
const controller = createSinglePaneController({ activateAux: true });
await settle();

Expand All @@ -3099,14 +3099,14 @@ suite('LayoutController (desktop)', () => {
// Switch to a new (uncreated) session; the working-set apply empties the
// group during the restore but the transient editor-change is NOT observed
// (it races the async close). Only the settled restore-end must re-open the
// Files tab.
// managed tabs.
harness.activeSessionObs.set(makeSession(URI.parse('session:new'), { status: SessionStatus.Untitled, isCreated: false }), undefined);
controller.runWithRestore(() => {
harness.activeGroupEditors.splice(0, harness.activeGroupEditors.length);
});
await settle();

assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: false, hasFilesTab: true });
assert.deepStrictEqual({ hasChangesTab: hasChangesTab(), hasFilesTab: hasFilesTab() }, { hasChangesTab: true, hasFilesTab: true });
});

test('[managed tabs / Scenario 9] removes the Files tab while a real editor is open and does not re-add it when that file closes', async () => {
Expand Down
Loading