From 44264fdeae4d4ae29845bc259559f93fb2cd143b Mon Sep 17 00:00:00 2001 From: Sandeep Somavarapu Date: Wed, 18 Mar 2026 14:43:31 +0100 Subject: [PATCH] Refactor new chat view: rename to workspace, separate session type and isolation pickers (#302797) * refactor: rename SessionProject to SessionWorkspace and update related references * fix: update filter placeholder text in project picker to "Search Workspaces..." * feat: add SessionWorkspace class to represent workspaces for sessions * feat: implement ProjectPicker class for unified project selection * fix: update SessionTargetType to use 'copilot-cli' instead of 'cli' * refactor: rename targetMode to isolationMode in NewSession and related classes * refactor: rename LocalNewSession to CopilotCLISession and update related references * feat: set project in session type picker for local sessions * fix: ensure project is set in session type picker for both remote and local sessions * fix: reset isolation mode to worktree when isolation option is disabled * delete file --- .../contrib/changes/browser/changesView.ts | 2 +- .../chat/browser/media/chatWelcomePart.css | 8 +- .../contrib/chat/browser/newChatViewPane.ts | 84 +++--- .../contrib/chat/browser/newSession.ts | 54 ++-- .../chat/browser/sessionTargetPicker.ts | 274 +++++++++++++----- .../{projectPicker.ts => workspacePicker.ts} | 42 +-- .../browser/fileTreeView.contribution.ts | 2 +- .../fileTreeView/browser/fileTreeView.ts | 2 +- .../browser/githubFileSystemProvider.ts | 2 +- .../github/test/browser/githubService.test.ts | 2 +- .../browser/sessionsManagementService.ts | 6 +- ...{sessionProject.ts => sessionWorkspace.ts} | 16 +- 12 files changed, 309 insertions(+), 185 deletions(-) rename src/vs/sessions/contrib/chat/browser/{projectPicker.ts => workspacePicker.ts} (90%) rename src/vs/sessions/contrib/sessions/common/{sessionProject.ts => sessionWorkspace.ts} (70%) diff --git a/src/vs/sessions/contrib/changes/browser/changesView.ts b/src/vs/sessions/contrib/changes/browser/changesView.ts index 889b669796d..c7d7e3bcd4b 100644 --- a/src/vs/sessions/contrib/changes/browser/changesView.ts +++ b/src/vs/sessions/contrib/changes/browser/changesView.ts @@ -58,7 +58,7 @@ import { ACTIVE_GROUP, IEditorService, SIDE_GROUP } from '../../../../workbench/ import { IExtensionService } from '../../../../workbench/services/extensions/common/extensions.js'; import { IWorkbenchLayoutService } from '../../../../workbench/services/layout/browser/layoutService.js'; import { IActiveSessionItem, ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionProject.js'; +import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionWorkspace.js'; import { CodeReviewStateKind, getCodeReviewFilesFromSessionChanges, getCodeReviewVersion, ICodeReviewService, PRReviewStateKind } from '../../codeReview/browser/codeReviewService.js'; import { IGitRepository, IGitService } from '../../../../workbench/contrib/git/common/gitService.js'; import { IGitHubService } from '../../github/browser/githubService.js'; diff --git a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css index 318195ec783..a3316e7d903 100644 --- a/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css +++ b/src/vs/sessions/contrib/chat/browser/media/chatWelcomePart.css @@ -178,7 +178,7 @@ } /* Prominent project picker button */ -.sessions-chat-picker-slot.sessions-chat-project-picker .action-label { +.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label { height: auto; padding: 8px 20px; font-size: 15px; @@ -188,16 +188,16 @@ border-radius: 6px; } -.sessions-chat-picker-slot.sessions-chat-project-picker .action-label:hover { +.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label:hover { background-color: var(--vscode-button-secondaryHoverBackground); } -.sessions-chat-picker-slot.sessions-chat-project-picker .action-label .codicon { +.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .codicon { font-size: 18px; margin-right: 6px; } -.sessions-chat-picker-slot.sessions-chat-project-picker .action-label .sessions-chat-dropdown-label { +.sessions-chat-picker-slot.sessions-chat-workspace-picker .action-label .sessions-chat-dropdown-label { font-size: 15px; } diff --git a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts index 4875fa441bd..6845c427983 100644 --- a/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts +++ b/src/vs/sessions/contrib/chat/browser/newChatViewPane.ts @@ -54,13 +54,12 @@ import { ContextMenuController } from '../../../../editor/contrib/contextmenu/br import { getSimpleEditorOptions } from '../../../../workbench/contrib/codeEditor/browser/simpleEditorOptions.js'; import { NewChatContextAttachments } from './newChatContextAttachments.js'; import { IGitService } from '../../../../workbench/contrib/git/common/gitService.js'; -import { TargetPicker } from './sessionTargetPicker.js'; +import { SessionTypePicker, IsolationPicker } from './sessionTargetPicker.js'; import { BranchPicker } from './branchPicker.js'; -import { SyncIndicator } from './syncIndicator.js'; import { INewSession, ISessionOptionGroup, RemoteNewSession } from './newSession.js'; import { CloudModelPicker } from './modelPicker.js'; -import { ProjectPicker } from './projectPicker.js'; -import { SessionProject } from '../../sessions/common/sessionProject.js'; +import { WorkspacePicker } from './workspacePicker.js'; +import { SessionWorkspace } from '../../sessions/common/sessionWorkspace.js'; import { ModePicker } from './modePicker.js'; import { getErrorMessage } from '../../../../base/common/errors.js'; import { SlashCommandHandler } from './slashCommands.js'; @@ -99,10 +98,10 @@ interface INewChatWidgetOptions { */ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { - private readonly _projectPicker: ProjectPicker; - private readonly _targetPicker: TargetPicker; + private readonly _workspacePicker: WorkspacePicker; + private readonly _sessionTypePicker: SessionTypePicker; private readonly _branchPicker: BranchPicker; - private readonly _syncIndicator: SyncIndicator; + private readonly _isolationPicker: IsolationPicker; private readonly _options: INewChatWidgetOptions; // IHistoryNavigationWidget @@ -185,17 +184,17 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { super(); this._history = this._register(this.instantiationService.createInstance(ChatHistoryNavigator, ChatAgentLocation.Chat)); this._contextAttachments = this._register(this.instantiationService.createInstance(NewChatContextAttachments)); - this._projectPicker = this._register(this.instantiationService.createInstance(ProjectPicker)); + this._workspacePicker = this._register(this.instantiationService.createInstance(WorkspacePicker)); this._permissionPicker = this._register(this.instantiationService.createInstance(NewChatPermissionPicker)); this._cloudModelPicker = this._register(this.instantiationService.createInstance(CloudModelPicker)); this._modePicker = this._register(this.instantiationService.createInstance(ModePicker)); - this._targetPicker = this._register(this.instantiationService.createInstance(TargetPicker)); + this._sessionTypePicker = this._register(this.instantiationService.createInstance(SessionTypePicker)); this._branchPicker = this._register(this.instantiationService.createInstance(BranchPicker)); - this._syncIndicator = this._register(this.instantiationService.createInstance(SyncIndicator)); + this._isolationPicker = this._register(this.instantiationService.createInstance(IsolationPicker)); this._options = options; // When a project is selected, infer the target and create a new session - this._register(this._projectPicker.onDidSelectProject(async (project) => { + this._register(this._workspacePicker.onDidSelectProject(async (project) => { await this._onProjectSelected(project); this._updateDraftState(); this._focusEditor(); @@ -208,15 +207,26 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._register(this._branchPicker.onDidChange((branch) => { this._newSession.value?.setBranch(branch); - this._syncIndicator.setBranch(branch); this._updateDraftState(); this._focusEditor(); })); - this._register(this._targetPicker.onDidChange((mode) => { - this._newSession.value?.setTargetMode(mode); + this._register(this._sessionTypePicker.onDidChange((target) => { + if (target === 'cloud') { + this._isolationPicker.setVisible(false); + this._branchPicker.setVisible(false); + } else { + this._newSession.value?.setIsolationMode(this._isolationPicker.isolationMode); + this._isolationPicker.setVisible(true); + this._branchPicker.setVisible(this._isolationPicker.isWorktree); + } + this._updateDraftState(); + this._focusEditor(); + })); + + this._register(this._isolationPicker.onDidChange((mode) => { + this._newSession.value?.setIsolationMode(mode); this._branchPicker.setVisible(mode === 'worktree'); - this._syncIndicator.setVisible(mode === 'worktree'); this._updateDraftState(); this._focusEditor(); })); @@ -281,12 +291,12 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { // Isolation mode and branch pickers (below the input, shown when Local target is selected) const isolationContainer = dom.append(welcomeElement, dom.$('.chat-full-welcome-local-mode')); - this._targetPicker.render(isolationContainer); + this._sessionTypePicker.render(isolationContainer); this._permissionPicker.render(isolationContainer); dom.append(isolationContainer, dom.$('.sessions-chat-local-mode-spacer')); const branchContainer = dom.append(isolationContainer, dom.$('.sessions-chat-local-mode-right')); + this._isolationPicker.render(branchContainer); this._branchPicker.render(branchContainer); - this._syncIndicator.render(branchContainer); // Render project picker & extension pickers this._renderOptionGroupPickers(); @@ -298,7 +308,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._restoreState(); // Create initial session - const restoredProject = this._projectPicker.selectedProject; + const restoredProject = this._workspacePicker.selectedProject; if (restoredProject) { this._onProjectSelected(restoredProject); } else { @@ -314,7 +324,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { }, { once: true })); } - private async _createNewSession(project?: SessionProject): Promise { + private async _createNewSession(project?: SessionWorkspace): Promise { const target = project?.isRepo ? AgentSessionProviders.Cloud : AgentSessionProviders.Background; const resource = getResourceForNewChatSession({ @@ -358,8 +368,9 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { } })); + this._sessionTypePicker.setProject(session.project); + if (session instanceof RemoteNewSession) { - this._targetPicker.setProject(session.project); this._renderRemoteSessionPickers(session, true); listeners.add(session.onDidChangeOptionGroups(() => { this._renderRemoteSessionPickers(session); @@ -382,7 +393,6 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._repositoryLoading = true; this._updateInputLoadingState(); this._branchPicker.setRepository(undefined); - this._syncIndicator.setRepository(undefined); this._modePicker.reset(); this.gitService.openRepository(folderUri).then(repository => { @@ -397,11 +407,10 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { session.setProject(session.project.withRepository(repository)); } - this._targetPicker.setProject(session?.project); + this._sessionTypePicker.setProject(session?.project); + this._isolationPicker.setHasGitRepo(!!repository); this._branchPicker.setRepository(repository); - this._branchPicker.setVisible(!!repository && this._targetPicker.isWorktree); - this._syncIndicator.setRepository(repository); - this._syncIndicator.setVisible(!!repository && this._targetPicker.isWorktree); + this._branchPicker.setVisible(!!repository && this._sessionTypePicker.isCli && this._isolationPicker.isWorktree); this._modePicker.reset(); }).catch(e => { if (cts.token.isCancellationRequested) { @@ -410,11 +419,10 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this.logService.warn(`Failed to open repository at ${folderUri.toString()}`, getErrorMessage(e)); this._repositoryLoading = false; this._updateInputLoadingState(); - this._targetPicker.setProject(undefined); + this._sessionTypePicker.setProject(undefined); + this._isolationPicker.setHasGitRepo(false); this._branchPicker.setRepository(undefined); this._branchPicker.setVisible(false); - this._syncIndicator.setRepository(undefined); - this._syncIndicator.setVisible(false); this._modePicker.reset(); }); } @@ -677,7 +685,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { const pickersRow = dom.append(this._pickersContainer, dom.$('.chat-full-welcome-pickers')); // Project picker (unified folder + repo picker) - this._projectPicker.render(pickersRow); + this._workspacePicker.render(pickersRow); } // --- Local session pickers --- @@ -703,7 +711,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { this._modePicker.setVisible(false); this._permissionPicker.setVisible(false); this._branchPicker.setVisible(false); - this._syncIndicator.setVisible(false); + this._isolationPicker.setVisible(false); this._cloudModelPicker.setSession(session); this._cloudModelPicker.setVisible(true); @@ -957,20 +965,20 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { } private _openRepoOrFolderPicker(_sessionType: AgentSessionProviders): void { - this._projectPicker.showPicker(); + this._workspacePicker.showPicker(); } - private async _requestFolderTrust(folderUri: URI, previousProject?: SessionProject): Promise { + private async _requestFolderTrust(folderUri: URI, previousProject?: SessionWorkspace): Promise { const trusted = await this.workspaceTrustRequestService.requestResourcesTrust({ uri: folderUri, message: localize('trustFolderMessage', "An agent session will be able to read files, run commands, and make changes in this folder."), }); if (!trusted) { - this._projectPicker.removeFromRecents(folderUri); + this._workspacePicker.removeFromRecents(folderUri); if (previousProject) { - this._projectPicker.setSelectedProject(previousProject, false); + this._workspacePicker.setSelectedProject(previousProject, false); } else { - this._projectPicker.clearSelection(); + this._workspacePicker.clearSelection(); } } return !!trusted; @@ -996,8 +1004,8 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { } if (draft.projectUri) { try { - const project = new SessionProject(URI.revive(draft.projectUri)); - this._projectPicker.setSelectedProject(project, false); + const project = new SessionWorkspace(URI.revive(draft.projectUri)); + this._workspacePicker.setSelectedProject(project, false); } catch { /* ignore */ } } } @@ -1054,7 +1062,7 @@ class NewChatWidget extends Disposable implements IHistoryNavigationWidget { * Infers the session target from the selection kind, creates a new session, * and shows/hides pickers accordingly. */ - private async _onProjectSelected(project: SessionProject): Promise { + private async _onProjectSelected(project: SessionWorkspace): Promise { // Cancel any in-flight project selection this._projectSelectionCts.value?.cancel(); const cts = this._projectSelectionCts.value = new CancellationTokenSource(); diff --git a/src/vs/sessions/contrib/chat/browser/newSession.ts b/src/vs/sessions/contrib/chat/browser/newSession.ts index e4e33ef5dd2..06ac91248a2 100644 --- a/src/vs/sessions/contrib/chat/browser/newSession.ts +++ b/src/vs/sessions/contrib/chat/browser/newSession.ts @@ -8,15 +8,15 @@ import { Disposable, IDisposable } from '../../../../base/common/lifecycle.js'; import { URI } from '../../../../base/common/uri.js'; import { ILogService } from '../../../../platform/log/common/log.js'; import { IChatSessionProviderOptionGroup, IChatSessionProviderOptionItem, IChatSessionsService } from '../../../../workbench/contrib/chat/common/chatSessionsService.js'; -import { TargetMode } from './sessionTargetPicker.js'; -import { SessionProject } from '../../sessions/common/sessionProject.js'; +import { IsolationMode } from './sessionTargetPicker.js'; +import { SessionWorkspace } from '../../sessions/common/sessionWorkspace.js'; import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js'; import { IChatRequestVariableEntry } from '../../../../workbench/contrib/chat/common/attachments/chatVariableEntries.js'; import { IChatMode } from '../../../../workbench/contrib/chat/common/chatModes.js'; -export type NewSessionChangeType = 'repoUri' | 'targetMode' | 'branch' | 'options' | 'disabled' | 'agent'; +export type NewSessionChangeType = 'repoUri' | 'isolationMode' | 'branch' | 'options' | 'disabled' | 'agent'; /** * Represents a resolved option group with its current selected value. @@ -28,14 +28,14 @@ export interface ISessionOptionGroup { /** * A new session represents a session being configured before the first - * request is sent. It holds the user's selections (repoUri, targetMode) + * request is sent. It holds the user's selections (repoUri, isolationMode) * and fires a single event when any property changes. */ export interface INewSession extends IDisposable { readonly resource: URI; readonly target: AgentSessionProviders; - readonly project: SessionProject | undefined; - readonly targetMode: TargetMode; + readonly project: SessionWorkspace | undefined; + readonly isolationMode: IsolationMode | undefined; readonly branch: string | undefined; readonly modelId: string | undefined; readonly mode: IChatMode | undefined; @@ -44,8 +44,8 @@ export interface INewSession extends IDisposable { readonly selectedOptions: ReadonlyMap; readonly disabled: boolean; readonly onDidChange: Event; - setProject(project: SessionProject): void; - setTargetMode(mode: TargetMode): void; + setProject(project: SessionWorkspace): void; + setIsolationMode(mode: IsolationMode): void; setBranch(branch: string | undefined): void; setModelId(modelId: string | undefined): void; setMode(mode: IChatMode | undefined): void; @@ -61,14 +61,14 @@ const AGENT_OPTION_ID = 'agent'; /** * Local new session for Background agent sessions. - * Fires `onDidChange` for both `repoUri` and `targetMode` changes. + * Fires `onDidChange` for both `repoUri` and `isolationMode` changes. * Notifies the extension service with session options for each property change. */ -export class LocalNewSession extends Disposable implements INewSession { +export class CopilotCLISession extends Disposable implements INewSession { private _repoUri: URI | undefined; - private _project: SessionProject | undefined; - private _targetMode: TargetMode; + private _project: SessionWorkspace | undefined; + private _isolationMode: IsolationMode; private _branch: string | undefined; private _modelId: string | undefined; private _mode: IChatMode | undefined; @@ -81,8 +81,8 @@ export class LocalNewSession extends Disposable implements INewSession { readonly target = AgentSessionProviders.Background; readonly selectedOptions = new Map(); - get project(): SessionProject | undefined { return this._project; } - get targetMode(): TargetMode { return this._targetMode; } + get project(): SessionWorkspace | undefined { return this._project; } + get isolationMode(): IsolationMode { return this._isolationMode; } get branch(): string | undefined { return this._branch; } get modelId(): string | undefined { return this._modelId; } get mode(): IChatMode | undefined { return this._mode; } @@ -92,7 +92,7 @@ export class LocalNewSession extends Disposable implements INewSession { if (!this._repoUri) { return true; } - if (this._targetMode === 'worktree' && !this._branch) { + if (this._isolationMode === 'worktree' && !this._branch) { return true; } return false; @@ -109,24 +109,24 @@ export class LocalNewSession extends Disposable implements INewSession { this._repoUri = defaultRepoUri; this.setOption(REPOSITORY_OPTION_ID, defaultRepoUri.fsPath); } - this._targetMode = 'worktree'; + this._isolationMode = 'worktree'; this.setOption(ISOLATION_OPTION_ID, 'worktree'); } - setProject(project: SessionProject): void { + setProject(project: SessionWorkspace): void { this._project = project; this._repoUri = project.uri; - this.setTargetMode('worktree'); + this.setIsolationMode('worktree'); this._branch = undefined; this._onDidChange.fire('repoUri'); this._onDidChange.fire('disabled'); this.setOption(REPOSITORY_OPTION_ID, project.uri.fsPath); } - setTargetMode(mode: TargetMode): void { - if (this._targetMode !== mode) { - this._targetMode = mode; - this._onDidChange.fire('targetMode'); + setIsolationMode(mode: IsolationMode): void { + if (this._isolationMode !== mode) { + this._isolationMode = mode; + this._onDidChange.fire('isolationMode'); this._onDidChange.fire('disabled'); this.setOption(ISOLATION_OPTION_ID, mode); } @@ -183,7 +183,7 @@ export class LocalNewSession extends Disposable implements INewSession { export class RemoteNewSession extends Disposable implements INewSession { private _repoUri: URI | undefined; - private _project: SessionProject | undefined; + private _project: SessionWorkspace | undefined; private _modelId: string | undefined; private _query: string | undefined; private _attachedContext: IChatRequestVariableEntry[] | undefined; @@ -196,8 +196,8 @@ export class RemoteNewSession extends Disposable implements INewSession { readonly selectedOptions = new Map(); - get project(): SessionProject | undefined { return this._project; } - get targetMode(): TargetMode { return 'cloud'; } + get project(): SessionWorkspace | undefined { return this._project; } + get isolationMode(): undefined { return undefined; } get branch(): string | undefined { return undefined; } get modelId(): string | undefined { return this._modelId; } get mode(): IChatMode | undefined { return undefined; } @@ -232,7 +232,7 @@ export class RemoteNewSession extends Disposable implements INewSession { })); } - setProject(project: SessionProject): void { + setProject(project: SessionWorkspace): void { this._project = project; this._repoUri = project.uri; this._onDidChange.fire('repoUri'); @@ -241,7 +241,7 @@ export class RemoteNewSession extends Disposable implements INewSession { this.setOption('repositories', { id, name: id }); } - setTargetMode(_mode: TargetMode): void { + setIsolationMode(_mode: IsolationMode): void { // No-op for remote sessions } diff --git a/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts index 373444fb378..c0dccecf85d 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionTargetPicker.ts @@ -12,49 +12,173 @@ import { localize } from '../../../../nls.js'; import { IActionWidgetService } from '../../../../platform/actionWidget/browser/actionWidget.js'; import { ActionListItemKind, IActionListDelegate, IActionListItem } from '../../../../platform/actionWidget/browser/actionList.js'; import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; -import { SessionProject } from '../../sessions/common/sessionProject.js'; +import { SessionWorkspace } from '../../sessions/common/sessionWorkspace.js'; + +// #region --- Types --- + +export type SessionTargetType = 'copilot-cli' | 'cloud'; +export type IsolationMode = 'worktree' | 'workspace'; + +// #endregion // #region --- Target Picker --- -export type TargetMode = 'worktree' | 'workspace' | 'cloud'; - /** - * A self-contained widget for selecting the session target mode. + * A self-contained widget for selecting the session target type. * * Options: - * - **Worktree** (`worktree`) — shown when a folder with a git repo is selected - * - **Folder** (`workspace`) — shown only when isolation option is enabled - * - **Cloud** (`cloud`) — shown and auto-selected when a repository is picked; disabled + * - **Copilot CLI** (`cli`) — local agent session + * - **Cloud** (`cloud`) — remote/cloud agent session * - * Emits `onDidChange` with the selected `TargetMode` when the user picks an option. + * The target is determined by the project type (folder → CLI, repo → Cloud). + * Emits `onDidChange` with the selected `SessionTargetType` when the target changes. */ -export class TargetPicker extends Disposable { +export class SessionTypePicker extends Disposable { - private _targetMode: TargetMode = 'worktree'; - private _project: SessionProject | undefined; - private _isolationOptionEnabled: boolean = true; + private _sessionTarget: SessionTargetType = 'copilot-cli'; + private _project: SessionWorkspace | undefined; - private readonly _onDidChange = this._register(new Emitter()); - readonly onDidChange: Event = this._onDidChange.event; + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; private readonly _renderDisposables = this._register(new DisposableStore()); private _slotElement: HTMLElement | undefined; private _triggerElement: HTMLElement | undefined; - get targetMode(): TargetMode { - return this._targetMode; + get sessionTarget(): SessionTargetType { + return this._sessionTarget; } - get isWorktree(): boolean { - return this._targetMode === 'worktree'; - } - - get isFolder(): boolean { - return this._targetMode === 'workspace'; + get isCli(): boolean { + return this._sessionTarget === 'copilot-cli'; } get isCloud(): boolean { - return this._targetMode === 'cloud'; + return this._sessionTarget === 'cloud'; + } + + constructor( + ) { + super(); + } + + /** + * Sets the current project context. Determines the target type: + * - Repo project → cloud + * - Folder project → cli + * - No project → retains current target + */ + setProject(project: SessionWorkspace | undefined): void { + this._project = project; + this._updateTarget(); + this._updateTriggerLabel(); + } + + private _updateTarget(): void { + if (this._project?.isRepo) { + this._setTarget('cloud'); + return; + } + + if (this._project?.isFolder) { + this._setTarget('copilot-cli'); + return; + } + } + + render(container: HTMLElement): void { + this._renderDisposables.clear(); + + const slot = dom.append(container, dom.$('.sessions-chat-picker-slot')); + this._slotElement = slot; + this._renderDisposables.add({ dispose: () => slot.remove() }); + + const trigger = dom.append(slot, dom.$('a.action-label')); + trigger.tabIndex = -1; + trigger.role = 'button'; + trigger.setAttribute('aria-disabled', 'true'); + this._triggerElement = trigger; + this._updateTriggerLabel(); + } + + private _setTarget(target: SessionTargetType): void { + if (this._sessionTarget !== target) { + this._sessionTarget = target; + this._updateTriggerLabel(); + this._onDidChange.fire(target); + } + } + + private _updateTriggerLabel(): void { + if (!this._triggerElement) { + return; + } + + dom.clearNode(this._triggerElement); + + let modeIcon; + let modeLabel: string; + + switch (this._sessionTarget) { + case 'cloud': + modeIcon = Codicon.cloud; + modeLabel = localize('sessionTarget.cloud', "Cloud"); + break; + case 'copilot-cli': + default: + modeIcon = Codicon.worktree; + modeLabel = localize('sessionTarget.cli', "Copilot CLI"); + break; + } + + dom.append(this._triggerElement, renderIcon(modeIcon)); + const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); + labelSpan.textContent = modeLabel; + + this._slotElement?.classList.toggle('disabled', true); + } +} + +// #endregion + +// #region --- Isolation Picker --- + +/** + * A self-contained widget for selecting the isolation mode. + * + * Options: + * - **Worktree** (`worktree`) — run in a git worktree + * - **Folder** (`workspace`) — run directly in the folder + * + * Only visible when isolation option is enabled, project has a git repo, + * and the target is CLI. + * + * Emits `onDidChange` with the selected `IsolationMode` when the user picks an option. + */ +export class IsolationPicker extends Disposable { + + private _isolationMode: IsolationMode = 'worktree'; + private _hasGitRepo = false; + private _visible = true; + private _isolationOptionEnabled: boolean; + + private readonly _onDidChange = this._register(new Emitter()); + readonly onDidChange: Event = this._onDidChange.event; + + private readonly _renderDisposables = this._register(new DisposableStore()); + private _slotElement: HTMLElement | undefined; + private _triggerElement: HTMLElement | undefined; + + get isolationMode(): IsolationMode { + return this._isolationMode; + } + + get isWorktree(): boolean { + return this._isolationMode === 'worktree'; + } + + get isFolder(): boolean { + return this._isolationMode === 'workspace'; } constructor( @@ -67,35 +191,37 @@ export class TargetPicker extends Disposable { this._register(this.configurationService.onDidChangeConfiguration(e => { if (e.affectsConfiguration('github.copilot.chat.cli.isolationOption.enabled')) { this._isolationOptionEnabled = this.configurationService.getValue('github.copilot.chat.cli.isolationOption.enabled') !== false; - this._updateMode(); + if (!this._isolationOptionEnabled) { + // Reset to worktree when isolation option is disabled + this._setMode('worktree'); + } + this._updateVisibility(); this._updateTriggerLabel(); } })); } /** - * Sets the current project context. Determines the available target modes: - * - Repo project → cloud mode (disabled picker) - * - Folder with git repo → retains current local mode (worktree/folder) - * - Folder without git repo → folder mode only - * - No project → retains current mode + * Sets whether the project has a git repository. + * Resets isolation mode to the appropriate default. */ - setProject(project: SessionProject | undefined): void { - this._project = project; - this._updateMode(); + setHasGitRepo(hasRepo: boolean): void { + this._hasGitRepo = hasRepo; + if (!hasRepo) { + this._setMode('workspace'); + } else { + this._setMode('worktree'); + } + this._updateVisibility(); this._updateTriggerLabel(); } - private _updateMode(): void { - if (this._project?.isRepo) { - this._setMode('cloud'); - return; - } - - if (this._project?.isFolder) { - this._setMode(this._project.repository ? 'worktree' : 'workspace'); - return; - } + /** + * Sets external visibility (e.g. hidden when target is Cloud). + */ + setVisible(visible: boolean): void { + this._visible = visible; + this._updateVisibility(); } render(container: HTMLElement): void { @@ -110,6 +236,7 @@ export class TargetPicker extends Disposable { trigger.role = 'button'; this._triggerElement = trigger; this._updateTriggerLabel(); + this._updateVisibility(); this._renderDisposables.add(dom.addDisposableListener(trigger, dom.EventType.CLICK, (e) => { dom.EventHelper.stop(e, true); @@ -125,35 +252,31 @@ export class TargetPicker extends Disposable { } private _showPicker(): void { - if (!this._triggerElement || this.actionWidgetService.isVisible || this._targetMode === 'cloud') { + if (!this._triggerElement || this.actionWidgetService.isVisible) { return; } - // No picker when there's no git repo — only Folder mode is available - if (!this._project?.repository) { + if (!this._hasGitRepo || !this._isolationOptionEnabled) { return; } - const items: IActionListItem[] = [ + const items: IActionListItem[] = [ { kind: ActionListItemKind.Action, - label: localize('targetMode.worktree', "Worktree"), + label: localize('isolationMode.worktree', "Worktree"), group: { title: '', icon: Codicon.worktree }, item: 'worktree', }, - ]; - - if (this._isolationOptionEnabled) { - items.push({ + { kind: ActionListItemKind.Action, - label: localize('targetMode.folder', "Folder"), + label: localize('isolationMode.folder', "Folder"), group: { title: '', icon: Codicon.folder }, item: 'workspace', - }); - } + }, + ]; const triggerElement = this._triggerElement; - const delegate: IActionListDelegate = { + const delegate: IActionListDelegate = { onSelect: (mode) => { this.actionWidgetService.hide(); this._setMode(mode); @@ -161,8 +284,8 @@ export class TargetPicker extends Disposable { onHide: () => { triggerElement.focus(); }, }; - this.actionWidgetService.show( - 'targetPicker', + this.actionWidgetService.show( + 'isolationPicker', false, items, delegate, @@ -171,19 +294,27 @@ export class TargetPicker extends Disposable { [], { getAriaLabel: (item) => item.label ?? '', - getWidgetAriaLabel: () => localize('targetPicker.ariaLabel', "Target"), + getWidgetAriaLabel: () => localize('isolationPicker.ariaLabel', "Isolation Mode"), }, ); } - private _setMode(mode: TargetMode): void { - if (this._targetMode !== mode) { - this._targetMode = mode; + private _setMode(mode: IsolationMode): void { + if (this._isolationMode !== mode) { + this._isolationMode = mode; this._updateTriggerLabel(); this._onDidChange.fire(mode); } } + private _updateVisibility(): void { + if (!this._slotElement) { + return; + } + const shouldShow = this._visible && this._hasGitRepo && this._isolationOptionEnabled; + this._slotElement.style.display = shouldShow ? '' : 'none'; + } + private _updateTriggerLabel(): void { if (!this._triggerElement) { return; @@ -193,25 +324,16 @@ export class TargetPicker extends Disposable { let modeIcon; let modeLabel: string; - let isDisabled: boolean = true; - if (this._project?.isFolder && this._project.repository) { - isDisabled = !this._isolationOptionEnabled; - } - - switch (this._targetMode) { - case 'cloud': - modeIcon = Codicon.cloud; - modeLabel = localize('targetMode.cloud', "Cloud"); - break; + switch (this._isolationMode) { case 'workspace': modeIcon = Codicon.folder; - modeLabel = localize('targetMode.folder', "Folder"); + modeLabel = localize('isolationMode.folder', "Folder"); break; case 'worktree': default: modeIcon = Codicon.worktree; - modeLabel = localize('targetMode.worktree', "Worktree"); + modeLabel = localize('isolationMode.worktree', "Worktree"); break; } @@ -219,12 +341,6 @@ export class TargetPicker extends Disposable { const labelSpan = dom.append(this._triggerElement, dom.$('span.sessions-chat-dropdown-label')); labelSpan.textContent = modeLabel; dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)); - - this._slotElement?.classList.toggle('disabled', isDisabled); - if (this._triggerElement) { - this._triggerElement.tabIndex = isDisabled ? -1 : 0; - this._triggerElement.setAttribute('aria-disabled', String(isDisabled)); - } } } diff --git a/src/vs/sessions/contrib/chat/browser/projectPicker.ts b/src/vs/sessions/contrib/chat/browser/workspacePicker.ts similarity index 90% rename from src/vs/sessions/contrib/chat/browser/projectPicker.ts rename to src/vs/sessions/contrib/chat/browser/workspacePicker.ts index 068df6f6c62..a31509424fe 100644 --- a/src/vs/sessions/contrib/chat/browser/projectPicker.ts +++ b/src/vs/sessions/contrib/chat/browser/workspacePicker.ts @@ -17,7 +17,7 @@ import { IFileDialogService } from '../../../../platform/dialogs/common/dialogs. import { IStorageService, StorageScope, StorageTarget } from '../../../../platform/storage/common/storage.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { renderIcon } from '../../../../base/browser/ui/iconLabel/iconLabels.js'; -import { GITHUB_REMOTE_FILE_SCHEME, SessionProject } from '../../sessions/common/sessionProject.js'; +import { GITHUB_REMOTE_FILE_SCHEME, SessionWorkspace } from '../../sessions/common/sessionWorkspace.js'; const OPEN_REPO_COMMAND = 'github.copilot.chat.cloudSessions.openRepository'; const STORAGE_KEY_LAST_PROJECT = 'sessions.lastPickedProject'; @@ -51,18 +51,18 @@ interface IStoredProject { * - "Browse Folders..." — opens a folder dialog * - "Browse Repositories..." — runs the cloud repository picker command */ -export class ProjectPicker extends Disposable { +export class WorkspacePicker extends Disposable { - private readonly _onDidSelectProject = this._register(new Emitter()); - readonly onDidSelectProject: Event = this._onDidSelectProject.event; + private readonly _onDidSelectProject = this._register(new Emitter()); + readonly onDidSelectProject: Event = this._onDidSelectProject.event; - private _selectedProject: SessionProject | undefined; + private _selectedProject: SessionWorkspace | undefined; private _recentProjects: IStoredProject[] = []; private _triggerElement: HTMLElement | undefined; private readonly _renderDisposables = this._register(new DisposableStore()); - get selectedProject(): SessionProject | undefined { + get selectedProject(): SessionWorkspace | undefined { return this._selectedProject; } @@ -134,7 +134,7 @@ export class ProjectPicker extends Disposable { try { const lastFolder = this.storageService.get(LEGACY_STORAGE_KEY_LAST_FOLDER, StorageScope.PROFILE); if (lastFolder) { - this._selectedProject = new SessionProject(URI.parse(lastFolder)); + this._selectedProject = new SessionWorkspace(URI.parse(lastFolder)); return; } } catch { /* ignore */ } @@ -143,7 +143,7 @@ export class ProjectPicker extends Disposable { const lastRepo = this.storageService.get(LEGACY_STORAGE_KEY_LAST_REPO, StorageScope.PROFILE); if (lastRepo) { const repo: { id: string; name: string } = JSON.parse(lastRepo); - this._selectedProject = new SessionProject(URI.from({ scheme: GITHUB_REMOTE_FILE_SCHEME, authority: 'github', path: `/${repo.id}/HEAD` })); + this._selectedProject = new SessionWorkspace(URI.from({ scheme: GITHUB_REMOTE_FILE_SCHEME, authority: 'github', path: `/${repo.id}/HEAD` })); } } catch { /* ignore */ } } @@ -155,7 +155,7 @@ export class ProjectPicker extends Disposable { render(container: HTMLElement): HTMLElement { this._renderDisposables.clear(); - const slot = dom.append(container, dom.$('.sessions-chat-picker-slot.sessions-chat-project-picker')); + const slot = dom.append(container, dom.$('.sessions-chat-picker-slot.sessions-chat-workspace-picker')); this._renderDisposables.add({ dispose: () => slot.remove() }); const trigger = dom.append(slot, dom.$('a.action-label')); @@ -207,10 +207,10 @@ export class ProjectPicker extends Disposable { onHide: () => { triggerElement.focus(); }, }; - const listOptions = showFilter ? { showFilter: true, filterPlaceholder: localize('projectPicker.filter', "Filter projects...") } : undefined; + const listOptions = showFilter ? { showFilter: true, filterPlaceholder: localize('workspacePicker.filter', "Search Workspaces...") } : undefined; this.actionWidgetService.show( - 'projectPicker', + 'workspacePicker', false, items, delegate, @@ -219,7 +219,7 @@ export class ProjectPicker extends Disposable { [], { getAriaLabel: (item) => item.label ?? '', - getWidgetAriaLabel: () => localize('projectPicker.ariaLabel', "Project Picker"), + getWidgetAriaLabel: () => localize('workspacePicker.ariaLabel', "Workspace Picker"), }, listOptions, ); @@ -229,7 +229,7 @@ export class ProjectPicker extends Disposable { * Programmatically set the selected project. * @param fireEvent Whether to fire the onDidSelectProject event. Defaults to true. */ - setSelectedProject(project: SessionProject, fireEvent = true): void { + setSelectedProject(project: SessionWorkspace, fireEvent = true): void { this._selectProject(project, fireEvent); } @@ -254,7 +254,7 @@ export class ProjectPicker extends Disposable { } } - private _selectProject(project: SessionProject, fireEvent = true): void { + private _selectProject(project: SessionWorkspace, fireEvent = true): void { this._selectedProject = project; const stored = this._toStored(project); this._addToRecents(stored); @@ -274,7 +274,7 @@ export class ProjectPicker extends Disposable { title: localize('selectFolder', "Select Folder"), }); if (selected?.[0]) { - this._selectProject(new SessionProject(selected[0])); + this._selectProject(new SessionWorkspace(selected[0])); } } catch { // dialog was cancelled or failed @@ -285,7 +285,7 @@ export class ProjectPicker extends Disposable { try { const result: string | undefined = await this.commandService.executeCommand(OPEN_REPO_COMMAND); if (result) { - this._selectProject(new SessionProject(URI.from({ scheme: GITHUB_REMOTE_FILE_SCHEME, authority: 'github', path: `/${result}/HEAD` }))); + this._selectProject(new SessionWorkspace(URI.from({ scheme: GITHUB_REMOTE_FILE_SCHEME, authority: 'github', path: `/${result}/HEAD` }))); } } catch { // command was cancelled or failed @@ -386,7 +386,7 @@ export class ProjectPicker extends Disposable { dom.clearNode(this._triggerElement); const project = this._selectedProject; - const label = project ? this._getProjectLabel(project) : localize('pickProject', "Pick a Project"); + const label = project ? this._getProjectLabel(project) : localize('pickWorkspace', "Pick a Workspace"); const icon = project ? (project.isFolder ? Codicon.folder : Codicon.repo) : Codicon.project; dom.append(this._triggerElement, renderIcon(icon)); @@ -395,7 +395,7 @@ export class ProjectPicker extends Disposable { dom.append(this._triggerElement, renderIcon(Codicon.chevronDown)); } - private _getProjectLabel(project: SessionProject): string { + private _getProjectLabel(project: SessionWorkspace): string { return this._getStoredProjectLabel({ uri: project.uri.toJSON() }); } @@ -408,14 +408,14 @@ export class ProjectPicker extends Disposable { return uri.path.substring(1).replace(/\/HEAD$/, ''); } - private _toStored(project: SessionProject): IStoredProject { + private _toStored(project: SessionWorkspace): IStoredProject { return { uri: project.uri.toJSON(), }; } - private _fromStored(stored: IStoredProject): SessionProject { - return new SessionProject(URI.revive(stored.uri)); + private _fromStored(stored: IStoredProject): SessionWorkspace { + return new SessionWorkspace(URI.revive(stored.uri)); } private _projectKey(project: IStoredProject): string { diff --git a/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.contribution.ts b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.contribution.ts index d82aad57f12..d9cf0cffe71 100644 --- a/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.contribution.ts +++ b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.contribution.ts @@ -8,7 +8,7 @@ import { IFileService } from '../../../../platform/files/common/files.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; import { GitHubFileSystemProvider } from './githubFileSystemProvider.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionProject.js'; +import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionWorkspace.js'; // --- View registration is currently disabled in favor of the "Add Context" picker. // The Files view will be re-enabled once we finalize the sessions auxiliary bar layout. diff --git a/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.ts b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.ts index 11230079baa..6b7d81ac7b3 100644 --- a/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.ts +++ b/src/vs/sessions/contrib/fileTreeView/browser/fileTreeView.ts @@ -44,7 +44,7 @@ import { ITelemetryService } from '../../../../platform/telemetry/common/telemet import { ILogService } from '../../../../platform/log/common/log.js'; import { ISessionsManagementService, IActiveSessionItem } from '../../sessions/browser/sessionsManagementService.js'; import { getGitHubRemoteFileDisplayName } from './githubFileSystemProvider.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionProject.js'; +import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionWorkspace.js'; import { basename } from '../../../../base/common/path.js'; import { isEqual } from '../../../../base/common/resources.js'; diff --git a/src/vs/sessions/contrib/fileTreeView/browser/githubFileSystemProvider.ts b/src/vs/sessions/contrib/fileTreeView/browser/githubFileSystemProvider.ts index 25e0089681c..4d567f570ab 100644 --- a/src/vs/sessions/contrib/fileTreeView/browser/githubFileSystemProvider.ts +++ b/src/vs/sessions/contrib/fileTreeView/browser/githubFileSystemProvider.ts @@ -12,7 +12,7 @@ import { IAuthenticationService } from '../../../../workbench/services/authentic import { CancellationToken } from '../../../../base/common/cancellation.js'; import { ILogService } from '../../../../platform/log/common/log.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionProject.js'; +import { GITHUB_REMOTE_FILE_SCHEME } from '../../sessions/common/sessionWorkspace.js'; /** * Derives a display name from a github-remote-file URI. diff --git a/src/vs/sessions/contrib/github/test/browser/githubService.test.ts b/src/vs/sessions/contrib/github/test/browser/githubService.test.ts index 9aa427dc2fe..5bbb3c83059 100644 --- a/src/vs/sessions/contrib/github/test/browser/githubService.test.ts +++ b/src/vs/sessions/contrib/github/test/browser/githubService.test.ts @@ -10,7 +10,7 @@ import { NullLogService, ILogService } from '../../../../../platform/log/common/ import { TestInstantiationService } from '../../../../../platform/instantiation/test/common/instantiationServiceMock.js'; import { GitHubService } from '../../browser/githubService.js'; import { URI } from '../../../../../base/common/uri.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../../../sessions/common/sessionProject.js'; +import { GITHUB_REMOTE_FILE_SCHEME } from '../../../sessions/common/sessionWorkspace.js'; import { IActiveSessionItem } from '../../../sessions/browser/sessionsManagementService.js'; suite('GitHubService', () => { diff --git a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts index 9f009fc0858..f243fead347 100644 --- a/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts +++ b/src/vs/sessions/contrib/sessions/browser/sessionsManagementService.ts @@ -21,12 +21,12 @@ import { IAgentSession, isAgentSession } from '../../../../workbench/contrib/cha import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js'; import { ICommandService } from '../../../../platform/commands/common/commands.js'; import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js'; -import { INewSession, LocalNewSession, RemoteNewSession } from '../../chat/browser/newSession.js'; +import { INewSession, CopilotCLISession, RemoteNewSession } from '../../chat/browser/newSession.js'; import { IUriIdentityService } from '../../../../platform/uriIdentity/common/uriIdentity.js'; import { isBuiltinChatMode } from '../../../../workbench/contrib/chat/common/chatModes.js'; import { ILanguageModelsService } from '../../../../workbench/contrib/chat/common/languageModels.js'; import { ILanguageModelToolsService } from '../../../../workbench/contrib/chat/common/tools/languageModelToolsService.js'; -import { GITHUB_REMOTE_FILE_SCHEME } from '../common/sessionProject.js'; +import { GITHUB_REMOTE_FILE_SCHEME } from '../common/sessionWorkspace.js'; import { IGitHubSessionContext } from '../../github/common/types.js'; import { ResourceSet } from '../../../../base/common/map.js'; @@ -267,7 +267,7 @@ export class SessionsManagementService extends Disposable implements ISessionsMa let newSession: INewSession; if (target === AgentSessionProviders.Background) { - newSession = this.instantiationService.createInstance(LocalNewSession, sessionResource, defaultRepoUri); + newSession = this.instantiationService.createInstance(CopilotCLISession, sessionResource, defaultRepoUri); } else { newSession = this.instantiationService.createInstance(RemoteNewSession, sessionResource, target); } diff --git a/src/vs/sessions/contrib/sessions/common/sessionProject.ts b/src/vs/sessions/contrib/sessions/common/sessionWorkspace.ts similarity index 70% rename from src/vs/sessions/contrib/sessions/common/sessionProject.ts rename to src/vs/sessions/contrib/sessions/common/sessionWorkspace.ts index 68127e14c5f..fe7fe1c2b41 100644 --- a/src/vs/sessions/contrib/sessions/common/sessionProject.ts +++ b/src/vs/sessions/contrib/sessions/common/sessionWorkspace.ts @@ -9,10 +9,10 @@ import { IGitRepository } from '../../../../workbench/contrib/git/common/gitServ export const GITHUB_REMOTE_FILE_SCHEME = 'github-remote-file'; /** - * Represents a project (folder or repository) for a session. - * The project type (folder vs repo) is derived from the URI scheme. + * Represents a workspace (folder or repository) for a session. + * The workspace type (folder vs repo) is derived from the URI scheme. */ -export class SessionProject { +export class SessionWorkspace { readonly uri: URI; readonly repository: IGitRepository | undefined; @@ -22,18 +22,18 @@ export class SessionProject { this.repository = repository; } - /** Whether this is a local folder project. */ + /** Whether this is a local folder workspace. */ get isFolder(): boolean { return this.uri.scheme !== GITHUB_REMOTE_FILE_SCHEME; } - /** Whether this is a remote repository project. */ + /** Whether this is a remote repository workspace. */ get isRepo(): boolean { return this.uri.scheme === GITHUB_REMOTE_FILE_SCHEME; } - /** Returns a new SessionProject with the repository updated. */ - withRepository(repository: IGitRepository | undefined): SessionProject { - return new SessionProject(this.uri, repository); + /** Returns a new SessionWorkspace with the repository updated. */ + withRepository(repository: IGitRepository | undefined): SessionWorkspace { + return new SessionWorkspace(this.uri, repository); } }