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
3 changes: 2 additions & 1 deletion .github/workflows/pr.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

permissions: {}
permissions:
contents: read

env:
VSCODE_QUALITY: 'oss'
Expand Down
1 change: 1 addition & 0 deletions .vscode/tasks.json
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,7 @@
"type": "shell",
"command": "npx component-explorer serve -c ./test/componentFixtures/component-explorer.json -vv --kill-if-running",
"isBackground": true,
"inSessions": true,
"problemMatcher": {
"owner": "component-explorer",
"fileLocation": "absolute",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import { DocumentSymbol, SymbolKind, SymbolKinds, SymbolTag, getAriaLabelForSymb
import { IOutlineModelService } from '../../documentSymbols/browser/outlineModel.js';
import { AbstractEditorNavigationQuickAccessProvider, IEditorNavigationQuickAccessOptions, IQuickAccessTextEditorContext } from './editorNavigationQuickAccess.js';
import { localize } from '../../../../nls.js';
import { IQuickInputButton, IQuickPick, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';
import { IKeyMods, IQuickInputButton, IQuickPick, IQuickPickDidAcceptEvent, IQuickPickItem, IQuickPickSeparator } from '../../../../platform/quickinput/common/quickInput.js';
import { ILanguageFeaturesService } from '../../../common/services/languageFeatures.js';
import { Position } from '../../../common/core/position.js';
import { findLast } from '../../../../base/common/arraysFind.js';
Expand All @@ -32,7 +32,7 @@ export interface IGotoSymbolQuickPickItem extends IQuickPickItem {
uri?: URI;
symbolName?: string;
range?: { decoration: IRange; selection: IRange };
attach?(): void;
attach?(keyMods: IKeyMods, event: IQuickPickDidAcceptEvent): void;
}

export interface IGotoSymbolQuickAccessProviderOptions extends IEditorNavigationQuickAccessOptions {
Expand Down Expand Up @@ -146,6 +146,13 @@ export abstract class AbstractGotoSymbolQuickAccessProvider extends AbstractEdit
disposables.add(picker.onDidAccept(event => {
const [item] = picker.selectedItems;
if (item && item.range) {
// When shift is held and attach is available, delegate to attach
// (e.g. to add to chat context) instead of navigating
if (picker.keyMods.shift && item.attach) {
item.attach(picker.keyMods, event);
return;
}

this.gotoLocation(context, { range: item.range.selection, keyMods: picker.keyMods, preserveFocus: event.inBackground });

runOptions?.handleAccept?.(item, event.inBackground);
Expand Down
3 changes: 3 additions & 0 deletions src/vs/platform/extensions/common/extensionsApiProposals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,9 @@ const _allApiProposals = {
chatReferenceDiagnostic: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatReferenceDiagnostic.d.ts',
},
chatSessionCustomizations: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSessionCustomizations.d.ts',
},
chatSessionsProvider: {
proposal: 'https://raw.githubusercontent.com/microsoft/vscode/main/src/vscode-dts/vscode.proposed.chatSessionsProvider.d.ts',
version: 3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ function makeSession(opts: { repository?: URI; worktree?: URI } = {}): ISessionD
lastTurnEnd: observableValue('lastTurnEnd', undefined),
description: observableValue('description', undefined),
pullRequestUri: observableValue('pullRequestUri', undefined),
pullRequestStateIcon: observableValue('pullRequestStateIcon', undefined),
};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Emitter, Event } from '../../../../base/common/event.js';
import { Codicon } from '../../../../base/common/codicons.js';
import { Disposable } from '../../../../base/common/lifecycle.js';
import { IObservable, observableValue, transaction } from '../../../../base/common/observable.js';
import { ThemeIcon } from '../../../../base/common/themables.js';
import { themeColorFromId, ThemeIcon } from '../../../../base/common/themables.js';
import { URI } from '../../../../base/common/uri.js';
import { ICommandService } from '../../../../platform/commands/common/commands.js';
import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs.js';
Expand Down Expand Up @@ -125,6 +125,7 @@ export class CopilotCLISession extends Disposable implements ISessionData {
readonly description: IObservable<string | undefined> = observableValue(this, undefined);
readonly lastTurnEnd: IObservable<Date | undefined> = observableValue(this, undefined);
readonly pullRequestUri: IObservable<URI | undefined> = observableValue(this, undefined);
readonly pullRequestStateIcon: IObservable<ThemeIcon | undefined> = observableValue(this, undefined);

private _gitRepository: IGitRepository | undefined;

Expand Down Expand Up @@ -301,6 +302,7 @@ export class RemoteNewSession extends Disposable implements ISessionData {
readonly description: IObservable<string | undefined> = observableValue(this, undefined);
readonly lastTurnEnd: IObservable<Date | undefined> = observableValue(this, undefined);
readonly pullRequestUri: IObservable<URI | undefined> = observableValue(this, undefined);
readonly pullRequestStateIcon: IObservable<ThemeIcon | undefined> = observableValue(this, undefined);

readonly _hasGitRepo = observableValue(this, false);
readonly hasGitRepo: IObservable<boolean> = this._hasGitRepo;
Expand Down Expand Up @@ -539,6 +541,9 @@ class AgentSessionAdapter implements ISessionData {
private readonly _pullRequestUri: ReturnType<typeof observableValue<URI | undefined>>;
readonly pullRequestUri: IObservable<URI | undefined>;

private readonly _pullRequestStateIcon: ReturnType<typeof observableValue<ThemeIcon | undefined>>;
readonly pullRequestStateIcon: IObservable<ThemeIcon | undefined>;

constructor(
session: IAgentSession,
providerId: string,
Expand Down Expand Up @@ -579,6 +584,8 @@ class AgentSessionAdapter implements ISessionData {
this.lastTurnEnd = this._lastTurnEnd;
this._pullRequestUri = observableValue(this, this._extractPullRequestUri(session));
this.pullRequestUri = this._pullRequestUri;
this._pullRequestStateIcon = observableValue(this, this._extractPullRequestStateIcon(session));
this.pullRequestStateIcon = this._pullRequestStateIcon;
}

/**
Expand All @@ -596,6 +603,7 @@ class AgentSessionAdapter implements ISessionData {
this._description.set(this._extractDescription(session), tx);
this._lastTurnEnd.set(session.timing.lastRequestEnded ? new Date(session.timing.lastRequestEnded) : undefined, tx);
this._pullRequestUri.set(this._extractPullRequestUri(session), tx);
this._pullRequestStateIcon.set(this._extractPullRequestStateIcon(session), tx);
});
}

Expand All @@ -606,6 +614,24 @@ class AgentSessionAdapter implements ISessionData {
return typeof session.description === 'string' ? session.description : session.description.value;
}

private _extractPullRequestStateIcon(session: IAgentSession): ThemeIcon | undefined {
const metadata = session.metadata;
const state = metadata?.pullRequestState;
if (state) {
switch (state) {
case 'merged':
return { ...Codicon.gitPullRequestDone, color: themeColorFromId('charts.purple') };
case 'closed':
return { ...Codicon.gitPullRequestClosed, color: themeColorFromId('charts.red') };
case 'draft':
return { ...Codicon.gitPullRequestDraft, color: themeColorFromId('descriptionForeground') };
default:
return { ...Codicon.gitPullRequest, color: themeColorFromId('charts.green') };
}
}
return undefined;
}

private _extractPullRequestUri(session: IAgentSession): URI | undefined {
const metadata = session.metadata;
if (!metadata) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,8 @@ export class RemoteAgentHostSessionsProvider extends Disposable implements ISess
description: observableValue(this, undefined),
lastTurnEnd: observableValue(this, undefined),
pullRequestUri: observableValue(this, undefined),
pullRequestStateIcon: observableValue(this, undefined),

};
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -123,10 +123,6 @@
color: var(--vscode-errorForeground);
}

&.session-icon-pr > .codicon {
color: var(--vscode-charts-green);
}

/* Small dot for read sessions — subtle indicator */
> .codicon.codicon-circle-small-filled {
color: var(--vscode-agentSessionReadIndicator-foreground);
Expand Down
34 changes: 19 additions & 15 deletions src/vs/sessions/contrib/sessions/browser/views/sessionsList.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@ import { IKeybindingService } from '../../../../../platform/keybinding/common/ke
import { ServiceCollection } from '../../../../../platform/instantiation/common/serviceCollection.js';
import { WorkbenchObjectTree } from '../../../../../platform/list/browser/listService.js';
import { IStyleOverride, defaultButtonStyles } from '../../../../../platform/theme/browser/defaultStyles.js';
import { asCssVariable } from '../../../../../platform/theme/common/colorUtils.js';
import { IStorageService, StorageScope, StorageTarget } from '../../../../../platform/storage/common/storage.js';
import { GITHUB_REMOTE_FILE_SCHEME, ISessionData, ISessionWorkspace, SessionStatus } from '../../common/sessionData.js';
import { ISessionsProvidersService } from '../sessionsProvidersService.js';
import { AgentSessionApprovalModel } from '../../../../../workbench/contrib/chat/browser/agentSessions/agentSessionApprovalModel.js';
import { Button } from '../../../../../base/browser/ui/button/button.js';
import { IMarkdownRendererService } from '../../../../../platform/markdown/browser/markdownRenderer.js';
import { Separator } from '../../../../../base/common/actions.js';
import { AgentSessionProviders } from '../../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js';

const $ = DOM.$;

Expand Down Expand Up @@ -215,20 +217,22 @@ class SessionItemRenderer implements ITreeRenderer<SessionListItem, FuzzyScore,
template.container.classList.toggle('archived', element.isArchived.read(reader));
}));


// Icon — reactive based on status, read state, and PR
template.elementDisposables.add(autorun(reader => {
const sessionStatus = element.status.read(reader);
const isRead = element.isRead.read(reader);
const isArchived = element.isArchived.read(reader);
const pullRequestUri = element.pullRequestUri.read(reader);
const pullRequestStateIcon = element.pullRequestStateIcon.read(reader);
DOM.clearNode(template.iconContainer);
const icon = this.getStatusIcon(sessionStatus, isRead, isArchived, !!pullRequestUri, element.icon);
DOM.append(template.iconContainer, $(`span${ThemeIcon.asCSSSelector(icon)}`));
template.iconContainer.classList.toggle('session-icon-pulse', sessionStatus === SessionStatus.NeedsInput);
template.iconContainer.classList.toggle('session-icon-active', sessionStatus === SessionStatus.InProgress);
template.iconContainer.classList.toggle('session-icon-error', sessionStatus === SessionStatus.Error);
template.iconContainer.classList.toggle('session-icon-unread', !isRead && !isArchived && sessionStatus !== SessionStatus.InProgress && sessionStatus !== SessionStatus.NeedsInput && sessionStatus !== SessionStatus.Error);
template.iconContainer.classList.toggle('session-icon-pr', !!pullRequestUri && sessionStatus === SessionStatus.Completed);
const hasPrIcon = !!pullRequestStateIcon;
const icon = hasPrIcon ? pullRequestStateIcon : this.getStatusIcon(sessionStatus, isRead, isArchived);
const iconSpan = DOM.append(template.iconContainer, $(`span${ThemeIcon.asCSSSelector(icon)}`));
iconSpan.style.color = icon.color ? asCssVariable(icon.color.id) : '';
template.iconContainer.classList.toggle('session-icon-pulse', !hasPrIcon && sessionStatus === SessionStatus.NeedsInput);
template.iconContainer.classList.toggle('session-icon-active', !hasPrIcon && sessionStatus === SessionStatus.InProgress);
template.iconContainer.classList.toggle('session-icon-error', !hasPrIcon && sessionStatus === SessionStatus.Error);
template.iconContainer.classList.toggle('session-icon-unread', !hasPrIcon && !isRead && !isArchived && sessionStatus !== SessionStatus.InProgress && sessionStatus !== SessionStatus.NeedsInput && sessionStatus !== SessionStatus.Error);
}));

// Title — reactive
Expand All @@ -251,9 +255,12 @@ class SessionItemRenderer implements ITreeRenderer<SessionListItem, FuzzyScore,
const parts: HTMLElement[] = [];

// Session type icon in details row
const typeIconEl = DOM.append(template.detailsRow, $('span.session-details-icon'));
DOM.append(typeIconEl, $(`span${ThemeIcon.asCSSSelector(element.icon)}`));
parts.push(typeIconEl);
// Disabling background icon - hacky but couldn't figure out how to do it from the new provider
if (element.sessionType !== AgentSessionProviders.Background) {
const typeIconEl = DOM.append(template.detailsRow, $('span.session-details-icon'));
DOM.append(typeIconEl, $(`span${ThemeIcon.asCSSSelector(element.icon)}`));
parts.push(typeIconEl);
}

// Workspace badge — show when not grouped by repository
if (workspace && this.options.grouping() !== SessionsGrouping.Repository) {
Expand Down Expand Up @@ -386,15 +393,12 @@ class SessionItemRenderer implements ITreeRenderer<SessionListItem, FuzzyScore,
}));
}

private getStatusIcon(status: SessionStatus, isRead: boolean, isArchived: boolean, hasPR: boolean, _defaultIcon: ThemeIcon): ThemeIcon {
private getStatusIcon(status: SessionStatus, isRead: boolean, isArchived: boolean): ThemeIcon {
switch (status) {
case SessionStatus.InProgress: return Codicon.sessionInProgress;
case SessionStatus.NeedsInput: return Codicon.circleFilled;
case SessionStatus.Error: return Codicon.error;
default:
if (hasPR) {
return Codicon.gitPullRequest;
}
if (!isRead && !isArchived) {
return Codicon.circleFilled;
}
Expand Down
2 changes: 2 additions & 0 deletions src/vs/sessions/contrib/sessions/common/sessionData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,6 @@ export interface ISessionData {
readonly lastTurnEnd: IObservable<Date | undefined>;
/** URI of the pull request associated with this session, if any. */
readonly pullRequestUri: IObservable<URI | undefined>;
/** Icon reflecting the PR state */
readonly pullRequestStateIcon: IObservable<ThemeIcon | undefined>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ function makeAgentSession(opts: {
lastTurnEnd: observableValue('test.lastTurnEnd', undefined),
description: observableValue('test.description', undefined),
pullRequestUri: observableValue('test.pullRequestUri', undefined),
pullRequestStateIcon: observableValue('test.pullRequestStateIcon', undefined),
};
}

Expand Down Expand Up @@ -104,6 +105,7 @@ function makeNonAgentSession(opts: { repository?: URI; worktree?: URI; providerT
lastTurnEnd: observableValue('test.lastTurnEnd', undefined),
description: observableValue('test.description', undefined),
pullRequestUri: observableValue('test.pullRequestUri', undefined),
pullRequestStateIcon: observableValue('test.pullRequestStateIcon', undefined),
};
}

Expand Down
Loading
Loading