mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-27 12:04:04 +01:00
Sessions - changes view improvements (#300780)
* Sessions - refactor context key managememt * Changes view improvements
This commit is contained in:
@@ -13,7 +13,7 @@ import { Codicon } from '../../../../base/common/codicons.js';
|
||||
import { MarkdownString } from '../../../../base/common/htmlContent.js';
|
||||
import { Iterable } from '../../../../base/common/iterator.js';
|
||||
import { DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js';
|
||||
import { autorun, derived, derivedOpts, IObservable, IObservableWithChange, observableFromEvent, observableFromPromise, observableValue } from '../../../../base/common/observable.js';
|
||||
import { autorun, constObservable, derived, derivedOpts, IObservable, IObservableWithChange, observableFromEvent, ObservablePromise, observableValue } from '../../../../base/common/observable.js';
|
||||
import { basename, dirname } from '../../../../base/common/path.js';
|
||||
import { extUriBiasedIgnorePathCase, isEqual } from '../../../../base/common/resources.js';
|
||||
import { ThemeIcon } from '../../../../base/common/themables.js';
|
||||
@@ -91,6 +91,8 @@ const enum ChangesVersionMode {
|
||||
}
|
||||
|
||||
const changesVersionModeContextKey = new RawContextKey<ChangesVersionMode>('sessions.changesVersionMode', ChangesVersionMode.AllChanges);
|
||||
const isMergeBaseBranchProtectedContextKey = new RawContextKey<boolean>('sessions.isMergeBaseBranchProtected', false);
|
||||
const hasOpenPullRequestContextKey = new RawContextKey<boolean>('sessions.hasOpenPullRequest', false);
|
||||
const hasUncommittedChangesContextKey = new RawContextKey<boolean>('sessions.hasUncommittedChanges', false);
|
||||
|
||||
// --- List Item
|
||||
@@ -263,7 +265,7 @@ export class ChangesViewPane extends ViewPane {
|
||||
private readonly activeSessionFileCountObs: IObservableWithChange<number>;
|
||||
private readonly activeSessionHasChangesObs: IObservableWithChange<boolean>;
|
||||
private readonly activeSessionRepositoryChangesObs: IObservableWithChange<IChangesFileItem[] | undefined>;
|
||||
private readonly activeSessionRepositoryObs: IObservableWithChange<IObservable<{ value?: IGitRepository }> | undefined>;
|
||||
private readonly activeSessionRepositoryObs: IObservableWithChange<IGitRepository | undefined>;
|
||||
|
||||
get activeSessionHasChanges(): IObservable<boolean> {
|
||||
return this.activeSessionHasChangesObs;
|
||||
@@ -325,22 +327,31 @@ export class ChangesViewPane extends ViewPane {
|
||||
}).recomputeInitiallyAndOnChange(this._store);
|
||||
|
||||
// Track active session repository changes
|
||||
this.activeSessionRepositoryObs = derived(reader => {
|
||||
const activeSessionRepositoryPromiseObs = derived(reader => {
|
||||
const activeSessionWorktree = this.activeSession.read(reader)?.worktree;
|
||||
if (!activeSessionWorktree) {
|
||||
return constObservable(undefined);
|
||||
}
|
||||
|
||||
return new ObservablePromise(this.gitService.openRepository(activeSessionWorktree)).resolvedValue;
|
||||
});
|
||||
|
||||
this.activeSessionRepositoryObs = derived<IGitRepository | undefined>(reader => {
|
||||
const activeSessionRepositoryPromise = activeSessionRepositoryPromiseObs.read(reader);
|
||||
if (activeSessionRepositoryPromise === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return observableFromPromise(this.gitService.openRepository(activeSessionWorktree));
|
||||
return activeSessionRepositoryPromise.read(reader);
|
||||
});
|
||||
|
||||
this.activeSessionRepositoryChangesObs = derived(reader => {
|
||||
const repository = this.activeSessionRepositoryObs.read(reader)?.read(reader);
|
||||
const repository = this.activeSessionRepositoryObs.read(reader);
|
||||
if (!repository) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const state = repository.value?.state.read(reader);
|
||||
const state = repository.state.read(reader);
|
||||
const headCommit = state?.HEAD?.commit;
|
||||
return (state?.workingTreeChanges ?? []).map(change => {
|
||||
const isDeletion = change.modifiedUri === undefined;
|
||||
@@ -615,34 +626,34 @@ export class ChangesViewPane extends ViewPane {
|
||||
// Reactively computes the diff between HEAD^ and HEAD. Memoize the diff observable so
|
||||
// that we only recompute it when the HEAD commit id actually changes.
|
||||
const headCommitObs = derived(reader => {
|
||||
const repository = this.activeSessionRepositoryObs.read(reader)?.read(reader)?.value;
|
||||
const repository = this.activeSessionRepositoryObs.read(reader);
|
||||
return repository?.state.read(reader)?.HEAD?.commit;
|
||||
});
|
||||
|
||||
const lastTurnChangesObs = derived(reader => {
|
||||
const repository = this.activeSessionRepositoryObs.read(reader)?.read(reader)?.value;
|
||||
const repository = this.activeSessionRepositoryObs.read(reader);
|
||||
const headCommit = headCommitObs.read(reader);
|
||||
if (!repository || !headCommit) {
|
||||
return undefined;
|
||||
return constObservable(undefined);
|
||||
}
|
||||
|
||||
return observableFromPromise(repository.diffBetweenWithStats(`${headCommit}^`, headCommit));
|
||||
return new ObservablePromise(repository.diffBetweenWithStats(`${headCommit}^`, headCommit)).resolvedValue;
|
||||
});
|
||||
|
||||
// Combine both entry sources for display
|
||||
const combinedEntriesObs = derived(reader => {
|
||||
const headCommit = headCommitObs.read(reader);
|
||||
const versionMode = this.versionModeObs.read(reader);
|
||||
const editEntries = editSessionEntriesObs.read(reader);
|
||||
const sessionFiles = sessionFilesObs.read(reader);
|
||||
const repositoryFiles = this.activeSessionRepositoryChangesObs.read(reader) ?? [];
|
||||
const versionMode = this.versionModeObs.read(reader);
|
||||
const lastTurnDiffChanges = lastTurnChangesObs.read(reader).read(reader);
|
||||
|
||||
let sourceEntries: IChangesFileItem[];
|
||||
if (versionMode === ChangesVersionMode.Uncommitted) {
|
||||
sourceEntries = repositoryFiles;
|
||||
} else if (versionMode === ChangesVersionMode.LastTurn) {
|
||||
const lastTurn = lastTurnChangesObs.read(reader);
|
||||
const diffChanges = lastTurn?.read(reader).value ?? [];
|
||||
const diffChanges = lastTurnDiffChanges ?? [];
|
||||
const parentRef = headCommit ? `${headCommit}^` : '';
|
||||
sourceEntries = diffChanges.map(change => {
|
||||
const isDeletion = change.modifiedUri === undefined;
|
||||
@@ -702,19 +713,18 @@ export class ChangesViewPane extends ViewPane {
|
||||
if (this.actionsContainer) {
|
||||
dom.clearNode(this.actionsContainer);
|
||||
|
||||
const scopedContextKeyService = this.renderDisposables.add(this.contextKeyService.createScoped(this.actionsContainer));
|
||||
const scopedInstantiationService = this.renderDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, scopedContextKeyService])));
|
||||
const scopedInstantiationService = this.renderDisposables.add(this.instantiationService.createChild(new ServiceCollection([IContextKeyService, this.scopedContextKeyService])));
|
||||
|
||||
// Set the chat session type context key reactively so that menu items with
|
||||
// `chatSessionType == copilotcli` (e.g. Create Pull Request) are shown
|
||||
const chatSessionTypeKey = scopedContextKeyService.createKey<string>(ChatContextKeys.agentSessionType.key, '');
|
||||
const chatSessionTypeKey = this.scopedContextKeyService.createKey<string>(ChatContextKeys.agentSessionType.key, '');
|
||||
this.renderDisposables.add(autorun(reader => {
|
||||
const activeSession = this.activeSession.read(reader);
|
||||
chatSessionTypeKey.set(activeSession?.sessionType ?? '');
|
||||
}));
|
||||
|
||||
// Bind required context keys for the menu buttons
|
||||
this.renderDisposables.add(bindContextKey(hasUndecidedChatEditingResourceContextKey, scopedContextKeyService, r => {
|
||||
this.renderDisposables.add(bindContextKey(hasUndecidedChatEditingResourceContextKey, this.scopedContextKeyService, r => {
|
||||
const session = activeEditingSessionObs.read(r);
|
||||
if (!session) {
|
||||
return false;
|
||||
@@ -723,7 +733,7 @@ export class ChangesViewPane extends ViewPane {
|
||||
return entries.some(entry => entry.state.read(r) === ModifiedFileEntryState.Modified);
|
||||
}));
|
||||
|
||||
this.renderDisposables.add(bindContextKey(hasAppliedChatEditsContextKey, scopedContextKeyService, r => {
|
||||
this.renderDisposables.add(bindContextKey(hasAppliedChatEditsContextKey, this.scopedContextKeyService, r => {
|
||||
const session = activeEditingSessionObs.read(r);
|
||||
if (!session) {
|
||||
return false;
|
||||
@@ -732,43 +742,40 @@ export class ChangesViewPane extends ViewPane {
|
||||
return entries.length > 0;
|
||||
}));
|
||||
|
||||
this.renderDisposables.add(bindContextKey(ChatContextKeys.hasAgentSessionChanges, scopedContextKeyService, r => {
|
||||
const { files } = topLevelStats.read(r);
|
||||
const hasAgentSessionChangesObs = derived(reader => {
|
||||
const { files } = topLevelStats.read(reader);
|
||||
return files > 0;
|
||||
}));
|
||||
});
|
||||
|
||||
// Also bind to the ViewPane's scoped context key service so the ViewTitle menu can evaluate it
|
||||
this.renderDisposables.add(bindContextKey(ChatContextKeys.hasAgentSessionChanges, this.scopedContextKeyService, r => {
|
||||
const { files } = topLevelStats.read(r);
|
||||
return files > 0;
|
||||
}));
|
||||
this.renderDisposables.add(bindContextKey(ChatContextKeys.hasAgentSessionChanges, this.scopedContextKeyService, r => hasAgentSessionChangesObs.read(r)));
|
||||
|
||||
// Track whether there are uncommitted (working tree) changes
|
||||
this.renderDisposables.add(bindContextKey(hasUncommittedChangesContextKey, this.scopedContextKeyService, r => {
|
||||
const repositoryFiles = this.activeSessionRepositoryChangesObs.read(r);
|
||||
const hasUncommittedChangesObs = derived(reader => {
|
||||
const repositoryFiles = this.activeSessionRepositoryChangesObs.read(reader);
|
||||
return (repositoryFiles?.length ?? 0) > 0;
|
||||
}));
|
||||
});
|
||||
|
||||
// Set context key for merge base branch protection
|
||||
const isMergeBaseBranchProtectedContextKey = scopedContextKeyService.createKey<boolean>('sessions.isMergeBaseBranchProtected', false);
|
||||
this.renderDisposables.add(autorun(reader => {
|
||||
const repository = this.activeSessionRepositoryObs.read(reader)?.read(reader).value;
|
||||
const state = repository?.state.read(reader);
|
||||
isMergeBaseBranchProtectedContextKey.set(state?.HEAD?.base?.isProtected === true);
|
||||
}));
|
||||
this.renderDisposables.add(bindContextKey(hasUncommittedChangesContextKey, this.scopedContextKeyService, r => hasUncommittedChangesObs.read(r)));
|
||||
|
||||
// Set context key for PR state from session metadata
|
||||
const hasOpenPullRequestKey = scopedContextKeyService.createKey<boolean>('sessions.hasOpenPullRequest', false);
|
||||
this.renderDisposables.add(autorun(reader => {
|
||||
const isMergeBaseBranchProtectedObs = derived(reader => {
|
||||
const state = this.activeSessionRepositoryObs.read(reader)?.state.read(reader);
|
||||
return state?.HEAD?.base?.isProtected === true;
|
||||
});
|
||||
|
||||
this.renderDisposables.add(bindContextKey(isMergeBaseBranchProtectedContextKey, this.scopedContextKeyService, r => isMergeBaseBranchProtectedObs.read(r)));
|
||||
|
||||
const hasOpenPullRequestObs = derived(reader => {
|
||||
const sessionResource = activeSessionResource.read(reader);
|
||||
sessionsChangedSignal.read(reader);
|
||||
if (sessionResource) {
|
||||
const metadata = this.agentSessionsService.getSession(sessionResource)?.metadata;
|
||||
hasOpenPullRequestKey.set(!!metadata?.pullRequestUrl);
|
||||
} else {
|
||||
hasOpenPullRequestKey.set(false);
|
||||
if (!sessionResource) {
|
||||
return false;
|
||||
}
|
||||
}));
|
||||
|
||||
sessionsChangedSignal.read(reader);
|
||||
|
||||
const metadata = this.agentSessionsService.getSession(sessionResource)?.metadata;
|
||||
return !!metadata?.pullRequestUrl;
|
||||
});
|
||||
|
||||
this.renderDisposables.add(bindContextKey(hasOpenPullRequestContextKey, this.scopedContextKeyService, r => hasOpenPullRequestObs.read(r)));
|
||||
|
||||
this.renderDisposables.add(autorun(reader => {
|
||||
const { isSessionMenu, added, removed } = topLevelStats.read(reader);
|
||||
|
||||
Reference in New Issue
Block a user