diff --git a/build/lib/stylelint/vscode-known-variables.json b/build/lib/stylelint/vscode-known-variables.json index 593a6366dbd202..96eefd71ed6f90 100644 --- a/build/lib/stylelint/vscode-known-variables.json +++ b/build/lib/stylelint/vscode-known-variables.json @@ -977,6 +977,7 @@ "others": [ "--action-widget-close-start-opacity", "--action-widget-close-start-transform", + "--activity-bar-action-gap", "--activity-bar-action-height", "--activity-bar-icon-size", "--activity-bar-width", diff --git a/build/win32/Cargo.lock b/build/win32/Cargo.lock index 8e974c66a4c984..1d889690230606 100644 --- a/build/win32/Cargo.lock +++ b/build/win32/Cargo.lock @@ -242,7 +242,7 @@ dependencies = [ [[package]] name = "inno_updater" -version = "0.22.0" +version = "0.23.0" dependencies = [ "byteorder", "crc", diff --git a/build/win32/Cargo.toml b/build/win32/Cargo.toml index b9849c5257ca08..486809f83426f2 100644 --- a/build/win32/Cargo.toml +++ b/build/win32/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "inno_updater" -version = "0.22.0" +version = "0.23.0" authors = ["Microsoft "] edition = "2024" build = "build.rs" diff --git a/build/win32/inno_updater.exe b/build/win32/inno_updater.exe index b5f36ceec373b7..7e47fb9824753d 100644 Binary files a/build/win32/inno_updater.exe and b/build/win32/inno_updater.exe differ diff --git a/src/vs/sessions/contrib/chat/browser/sessionArtifacts.ts b/src/vs/sessions/contrib/chat/browser/sessionArtifacts.ts index 594b46ef69db84..205cd1d75f5b02 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionArtifacts.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionArtifacts.ts @@ -86,6 +86,28 @@ function getImageMimeType(uri: URI): string | undefined { return mimeType?.startsWith('image/') ? mimeType : undefined; } +/** + * A comparison key for a website URL that ignores origin casing and a trailing + * slash, so an artifact and the browser showing it are recognized as the same page. + */ +function websiteKey(url: string): string | undefined { + const parsed = URL.parse(url); + if (!parsed) { + return undefined; + } + const path = parsed.pathname.length > 1 && parsed.pathname.endsWith('/') ? parsed.pathname.slice(0, -1) : parsed.pathname; + return `${parsed.protocol}//${parsed.host}${path}${parsed.search}${parsed.hash}`; +} + +/** Whether a website artifact points at a page one of the listed browsers shows. */ +function isShownInBrowser(link: URI | undefined, browserKeys: ReadonlySet): boolean { + if (!link || browserKeys.size === 0) { + return false; + } + const key = websiteKey(link.toString()); + return !!key && browserKeys.has(key); +} + function toEntry(artifact: ISessionArtifact, actions: ISessionArtifactActions): IChatPillEntry | undefined { if (artifact.kind === SessionArtifactKind.File) { if (!artifact.uri) { @@ -131,14 +153,25 @@ function toEntry(artifact: ISessionArtifact, actions: ISessionArtifactActions): /** * Builds the artifact sections shown in the pill: the agent-set artifacts plus * the previewable files the session wrote outside its workspace, de-duplicated - * with the agent's own entries winning. + * with the agent's own entries winning. Websites the browsers pill already lists + * are left out, so the same page is offered once across the two pills. */ -export function buildSessionArtifactSections(artifacts: readonly ISessionArtifact[], externalFiles: readonly ISessionFile[], actions: ISessionArtifactActions, imageCarouselEnabled: boolean): readonly IChatPillSection[] { +export function buildSessionArtifactSections(artifacts: readonly ISessionArtifact[], externalFiles: readonly ISessionFile[], actions: ISessionArtifactActions, imageCarouselEnabled: boolean, browserUrls: ReadonlySet): readonly IChatPillSection[] { const entriesByKind = new Map(); const images: ISessionArtifactImage[] = []; const seen = new Set(); + const browserKeys = new Set(); + for (const url of browserUrls) { + const key = websiteKey(url); + if (key) { + browserKeys.add(key); + } + } for (const artifact of artifacts) { + if (artifact.kind === SessionArtifactKind.Website && isShownInBrowser(artifact.link, browserKeys)) { + continue; + } const imageMimeType = artifact.uri ? getImageMimeType(artifact.uri) : undefined; if (artifact.kind === SessionArtifactKind.File && artifact.uri && imageMimeType) { if (!seen.has(artifactValueKey(artifact))) { @@ -210,6 +243,8 @@ export class SessionArtifacts extends Disposable { constructor( session: IObservable, + /** The URLs the browsers pill lists; website artifacts for them are left out. */ + private readonly _browserUrls: IObservable>, @IClipboardService private readonly _clipboardService: IClipboardService, @ICommandService private readonly _commandService: ICommandService, @IConfigurationService private readonly _configurationService: IConfigurationService, @@ -229,6 +264,7 @@ export class SessionArtifacts extends Disposable { this._readExternalFiles(current, reader), this._actions(), imageCarouselEnabled.read(reader), + this._browserUrls.read(reader), ); }); } @@ -239,7 +275,10 @@ export class SessionArtifacts extends Disposable { private _actions(): ISessionArtifactActions { return { - openExternal: link => { void this._openerService.open(link, { openExternal: true }); }, + // Contributed openers make a link behave the same here as in the response + // markdown it came from, so a localhost page lands in the integrated + // browser rather than the system one. + openExternal: link => { void this._openerService.open(link, { openExternal: true, allowContributedOpeners: true, fromUserGesture: true }); }, openResource: uri => { if (previewKind(uri)) { void openChatTurnFile({ uri, kind: previewKind(uri)!, created: false }, this._openerService, this._configurationService); diff --git a/src/vs/sessions/contrib/chat/browser/sessionBrowsersControl.ts b/src/vs/sessions/contrib/chat/browser/sessionBrowsersControl.ts index d6cf0fa18b081d..d5dab1934fb970 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionBrowsersControl.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionBrowsersControl.ts @@ -5,7 +5,7 @@ import { Codicon } from '../../../../base/common/codicons.js'; import { Disposable, DisposableStore, MutableDisposable } from '../../../../base/common/lifecycle.js'; -import { derived, IObservable, IReader, observableSignal, observableValue } from '../../../../base/common/observable.js'; +import { derived, derivedOpts, IObservable, IReader, observableSignal, observableValue } from '../../../../base/common/observable.js'; import { isEqual } from '../../../../base/common/resources.js'; import { localize } from '../../../../nls.js'; import { BrowserEditorInput } from '../../../../workbench/contrib/browserView/common/browserEditorInput.js'; @@ -26,11 +26,30 @@ export const sessionBrowsersPillOptions: IChatDropdownPillOptions = { summaryAriaLabel: count => localize('browsers.show', "Show {0} browsers", count), }; +const NO_URLS: ReadonlySet = new Set(); + +function urlsEqual(a: ReadonlySet, b: ReadonlySet): boolean { + if (a === b) { + return true; + } + if (a.size !== b.size) { + return false; + } + for (const url of a) { + if (!b.has(url)) { + return false; + } + } + return true; +} + /** Supplies the live browsers of the viewed chat (and its subagents) to its pill. */ export class SessionBrowsersControl extends Disposable { /** The pill's sections, empty while the user has the pill hidden. */ readonly sections: IObservable; + /** The URLs the pill's browsers show, empty while the user has the pill hidden. */ + readonly urls: IObservable>; /** Whether there are browsers to show, regardless of the user's visibility choice. */ readonly hasData: IObservable; @@ -49,25 +68,45 @@ export class SessionBrowsersControl extends Disposable { ) { super(); - const allSections = derived(this, reader => { + // The browsers the pill lists, before the user's visibility choice. Empty while + // the debug overlay supplies its own browsers in their place. + const allBrowsers = derived(this, reader => { this._browsersChanged.read(reader); - const debugData = this._debugData.read(reader); const currentSession = session.read(reader); const currentChat = chat.read(reader); + return !this._debugData.read(reader) && enabled.read(reader) && currentSession && currentChat + // Read the chat list through the reader so browsers registered by a + // subagent show up as soon as that subagent joins the session. + ? this._collectBrowsers(this._collectOwnerIds(currentSession, currentChat, reader)) + : []; + }); + + const allSections = derived(this, reader => { + const debugData = this._debugData.read(reader); + const currentChat = chat.read(reader); const browsers = debugData ? debugData.browsers.map(label => this._entry(label, undefined, currentChat)) - : enabled.read(reader) && currentSession && currentChat - // Read the chat list through the reader so browsers registered by a - // subagent show up as soon as that subagent joins the session. - ? this._collectBrowsers(this._collectOwnerIds(currentSession, currentChat, reader), currentChat) - : []; + : allBrowsers.read(reader).map(input => this._entry(input.title?.trim() || localize('browsers.browser', "Browser"), input, currentChat)); return browsers.length > 0 ? [{ title: localize('browsers.browsers', "Browsers"), entries: browsers }] : []; }); + // Browser titles and loading states change far more often than the pages + // themselves, so only report a genuinely different set of URLs. + const allUrls = derivedOpts>({ owner: this, equalsFn: urlsEqual }, reader => { + const urls = new Set(); + for (const input of allBrowsers.read(reader)) { + if (input.url) { + urls.add(input.url); + } + } + return urls; + }); + this.hasData = derived(this, reader => getChatPillEntries(allSections.read(reader)).length > 0); this.sections = derived(this, reader => visible.read(reader) ? allSections.read(reader) : []); + this.urls = derivedOpts>({ owner: this, equalsFn: urlsEqual }, reader => visible.read(reader) ? allUrls.read(reader) : NO_URLS); this._register(this._browserViewService.onDidChangeBrowserViews(() => this._refreshBrowserListeners())); this._refreshBrowserListeners(); @@ -96,15 +135,15 @@ export class SessionBrowsersControl extends Disposable { return ownerIds; } - private _collectBrowsers(ownerIds: ReadonlySet, chat: IChat | undefined): IChatPillEntry[] { - const entries: IChatPillEntry[] = []; + private _collectBrowsers(ownerIds: ReadonlySet): BrowserEditorInput[] { + const inputs: BrowserEditorInput[] = []; for (const input of this._browserViewService.getKnownBrowserViews().values()) { const ownerId = input.model?.owner.type === 'agent' ? input.model.owner.sessionId : undefined; if (ownerId && ownerIds.has(ownerId)) { - entries.push(this._entry(input.title?.trim() || localize('browsers.browser', "Browser"), input, chat)); + inputs.push(input); } } - return entries; + return inputs; } private _entry(label: string, input: BrowserEditorInput | undefined, chat: IChat | undefined): IChatPillEntry { diff --git a/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts b/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts index dce749fb0934c6..605ca8e4479e1c 100644 --- a/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts +++ b/src/vs/sessions/contrib/chat/browser/sessionChatInputToolbar.ts @@ -148,7 +148,13 @@ export class SessionChatInputToolbar extends Disposable { return chat ? computeTurnStats(chat, reader) : EMPTY_DIFF_STATS; }); - const sessionArtifacts = this._register(instantiationService.createInstance(SessionArtifacts, this._session)); + const turnStatusPillsEnabled = observeTurnStatusPillsEnabled(this._configurationService); + const visibility = this._register(instantiationService.createInstance(SessionChatPillVisibility)); + this._browsers = this._register(instantiationService.createInstance(SessionBrowsersControl, this._session, this._chat, turnStatusPillsEnabled, derived(reader => visibility.isVisible(SessionChatPillKind.Browsers, reader)))); + + // The browsers pill already offers the pages it lists, so the artifacts pill + // leaves those websites out. + const sessionArtifacts = this._register(instantiationService.createInstance(SessionArtifacts, this._session, this._browsers.urls)); this._artifactSections = derived(this, reader => { const debugData = this._debugData.read(reader); return debugData ? buildDebugArtifactSections(debugData) : sessionArtifacts.sections.read(reader); @@ -156,7 +162,6 @@ export class SessionChatInputToolbar extends Disposable { const sessionCustomizations = this._register(instantiationService.createInstance(SessionCustomizations, this._chat, this._session)); this._customizationSections = sessionCustomizations.sections; - const turnStatusPillsEnabled = observeTurnStatusPillsEnabled(this._configurationService); const pillsEnabled = derived(reader => this._debugData.read(reader) !== undefined || turnStatusPillsEnabled.read(reader)); const model: IChatTurnPillsModel = { stats: this._diffStats, @@ -168,7 +173,6 @@ export class SessionChatInputToolbar extends Disposable { const turnPills = this._register(instantiationService.createInstance(ChatTurnPillsProvider, model)); const metadataPills = this._register(instantiationService.createInstance(SessionMetadataPills, this.element, this._session)); - const visibility = this._register(instantiationService.createInstance(SessionChatPillVisibility)); // Every pill the session currently has data for, before the user's // per-kind visibility choices are applied. @@ -179,7 +183,6 @@ export class SessionChatInputToolbar extends Disposable { ...turn.filter(pill => pill.action.id !== CHAT_TURN_CHANGES_PILL_ID), ]; }); - this._browsers = this._register(instantiationService.createInstance(SessionBrowsersControl, this._session, this._chat, turnStatusPillsEnabled, derived(reader => visibility.isVisible(SessionChatPillKind.Browsers, reader)))); this._backgroundActivities = this._register(instantiationService.createInstance(SessionBackgroundActivitiesControl, this._session, this._chat, turnStatusPillsEnabled, derived(reader => visibility.isVisible(SessionChatPillKind.Subagents, reader)))); // `show-file-icons` lets a resource pill paint its themed file icon. diff --git a/src/vs/sessions/contrib/chat/test/browser/sessionArtifacts.test.ts b/src/vs/sessions/contrib/chat/test/browser/sessionArtifacts.test.ts index 13474f47aeb3b2..e69f59e7cc93b6 100644 --- a/src/vs/sessions/contrib/chat/test/browser/sessionArtifacts.test.ts +++ b/src/vs/sessions/contrib/chat/test/browser/sessionArtifacts.test.ts @@ -31,7 +31,7 @@ suite('Session Artifacts', () => { { id: 'resource', kind: SessionArtifactKind.Resource, label: 'Resource', uri: resourceUri }, ]; - const entries = buildSessionArtifactSections(artifacts, [{ uri: externalFileUri, operation: SessionFileOperation.Created }], actions, true).flatMap(section => section.entries); + const entries = buildSessionArtifactSections(artifacts, [{ uri: externalFileUri, operation: SessionFileOperation.Created }], actions, true, new Set()).flatMap(section => section.entries); assert.deepStrictEqual(entries.map(entry => { const content = entry.hover?.content; return { @@ -49,4 +49,26 @@ suite('Session Artifacts', () => { ]); }); + test('leaves out websites the browsers pill already lists', () => { + const pullRequestLink = URI.parse('https://github.com/microsoft/vscode/pull/12'); + const artifacts: readonly ISessionArtifact[] = [ + { id: 'docs', kind: SessionArtifactKind.Website, label: 'Docs', link: URI.parse('https://example.com/docs') }, + { id: 'docs-slash', kind: SessionArtifactKind.Website, label: 'Docs Index', link: URI.parse('https://Example.com/docs/') }, + { id: 'deep', kind: SessionArtifactKind.Website, label: 'Deep Link', link: URI.parse('https://example.com/docs/api') }, + { id: 'blog', kind: SessionArtifactKind.Website, label: 'Blog', link: URI.parse('https://other.test/blog') }, + { id: 'pr', kind: SessionArtifactKind.PullRequest, label: 'PR #12', link: pullRequestLink }, + ]; + const labels = (browserUrls: readonly string[]) => buildSessionArtifactSections(artifacts, [], actions, true, new Set(browserUrls)) + .flatMap(section => section.entries) + .map(entry => entry.label); + + assert.deepStrictEqual({ + withBrowsers: labels(['https://example.com/docs', pullRequestLink.toString()]), + withoutBrowsers: labels([]), + }, { + withBrowsers: ['PR #12', 'Deep Link', 'Blog'], + withoutBrowsers: ['PR #12', 'Docs', 'Docs Index', 'Deep Link', 'Blog'], + }); + }); + }); diff --git a/src/vs/sessions/contrib/chat/test/browser/sessionBrowsersControl.test.ts b/src/vs/sessions/contrib/chat/test/browser/sessionBrowsersControl.test.ts index 381c99af1215aa..a7c3201b6758c8 100644 --- a/src/vs/sessions/contrib/chat/test/browser/sessionBrowsersControl.test.ts +++ b/src/vs/sessions/contrib/chat/test/browser/sessionBrowsersControl.test.ts @@ -260,4 +260,23 @@ suite('SessionBrowsersControl', () => { fallback: 'browser-0', }); }); + + test('publishes the listed browser URLs only while the pill is visible', () => { + const browsers = [ + { title: 'Docs', url: 'https://example.com/docs' }, + { title: 'Subagent Preview', url: 'https://preview.test/', owner: 'subagent' as const }, + { title: 'Other Session', url: 'https://other.test/', owner: 'other' as const }, + { title: 'Blank' }, + ]; + + assert.deepStrictEqual({ + visible: [...createControl({ browsers }, store).control.urls.get()], + hidden: [...createControl({ browsers, visible: false }, store).control.urls.get()], + disabled: [...createControl({ browsers, enabled: false }, store).control.urls.get()], + }, { + visible: ['https://example.com/docs', 'https://preview.test/'], + hidden: [], + disabled: [], + }); + }); }); diff --git a/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts b/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts index 0beae6ba6931a0..f8743c0f92e48f 100644 --- a/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts +++ b/src/vs/sessions/contrib/chat/test/browser/sessionChatInputToolbar.test.ts @@ -15,7 +15,7 @@ import { SESSION_CUSTOMIZATIONS_PILL_ID } from '../../browser/sessionCustomizati suite('SessionChatInputToolbar', () => { ensureNoDisposablesAreLeakedInTestSuite(); - test('maps turn-status, contributed metadata and hosted pill actions onto togglable pill kinds', () => { + test('maps turn-status and hosted pill actions onto togglable pill kinds', () => { assert.deepStrictEqual([ getSessionChatPillKindForAction(CHAT_TURN_CHANGES_PILL_ID), getSessionChatPillKindForAction(VIEW_SESSION_CHANGES_COMMAND_ID), @@ -25,7 +25,6 @@ suite('SessionChatInputToolbar', () => { getSessionChatPillKindForAction(OPEN_ISSUE_ACTION_ID), getSessionChatPillKindForAction(SESSION_BROWSERS_PILL_ID), getSessionChatPillKindForAction(SESSION_SUBAGENTS_PILL_ID), - getSessionChatPillKindForAction('workbench.agentSessions.action.openFilesView'), ], [ SessionChatPillKind.Changes, SessionChatPillKind.Changes, @@ -35,7 +34,6 @@ suite('SessionChatInputToolbar', () => { SessionChatPillKind.Issues, SessionChatPillKind.Browsers, SessionChatPillKind.Subagents, - undefined, ]); }); }); diff --git a/src/vs/sessions/contrib/files/browser/files.contribution.ts b/src/vs/sessions/contrib/files/browser/files.contribution.ts index 40643c99f500c7..cd9892da0ba498 100644 --- a/src/vs/sessions/contrib/files/browser/files.contribution.ts +++ b/src/vs/sessions/contrib/files/browser/files.contribution.ts @@ -19,7 +19,6 @@ import { ViewPaneContainer } from '../../../../workbench/browser/parts/views/vie import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js'; import { IsSessionsWindowContext, WorkspaceFolderCountContext } from '../../../../workbench/common/contextkeys.js'; import { SESSIONS_FILES_EMPTY_VIEW_ID, SESSIONS_FILES_VIEW_ID, SessionsExplorerEmptyView, SessionsExplorerView } from './filesView.js'; -import './workspaceFolderActions.js'; import { KeyCode, KeyMod } from '../../../../base/common/keyCodes.js'; import { SessionHasGitRepositoryContext, SessionHasGitSyncActionRunningContext, IsNewChatSessionContext, IsPhoneLayoutContext, SessionHasWorkspaceContext } from '../../../common/contextkeys.js'; import { IWorkspaceContextService } from '../../../../platform/workspace/common/workspace.js'; diff --git a/src/vs/sessions/contrib/files/browser/workspaceFolderActions.ts b/src/vs/sessions/contrib/files/browser/workspaceFolderActions.ts deleted file mode 100644 index 52bc9847437bf1..00000000000000 --- a/src/vs/sessions/contrib/files/browser/workspaceFolderActions.ts +++ /dev/null @@ -1,191 +0,0 @@ -/*--------------------------------------------------------------------------------------------- - * Copyright (c) Microsoft Corporation. All rights reserved. - * Licensed under the MIT License. See License.txt in the project root for license information. - *--------------------------------------------------------------------------------------------*/ - -import { $ } from '../../../../base/browser/dom.js'; -import { IActionViewItemOptions } from '../../../../base/browser/ui/actionbar/actionViewItems.js'; -import { IManagedHoverContent } from '../../../../base/browser/ui/hover/hover.js'; -import { Codicon } from '../../../../base/common/codicons.js'; -import { structuralEquals } from '../../../../base/common/equals.js'; -import { Emitter } from '../../../../base/common/event.js'; -import { MarkdownString } from '../../../../base/common/htmlContent.js'; -import { Disposable } from '../../../../base/common/lifecycle.js'; -import { autorun, derivedOpts, IObservable } from '../../../../base/common/observable.js'; -import { ThemeIcon } from '../../../../base/common/themables.js'; -import { localize, localize2 } from '../../../../nls.js'; -import { IActionViewItemService } from '../../../../platform/actions/browser/actionViewItemService.js'; -import { Action2, MenuItemAction, registerAction2 } from '../../../../platform/actions/common/actions.js'; -import { ICommandService } from '../../../../platform/commands/common/commands.js'; -import { ContextKeyExpr } from '../../../../platform/contextkey/common/contextkey.js'; -import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js'; -import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; -import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js'; -import { IAgentWorkbenchLayoutService } from '../../../browser/workbench.js'; -import { Menus } from '../../../browser/menus.js'; -import { getSessionWorkspaceDisplayInfo, ISessionWorkspaceDisplayInfo } from '../../../browser/sessionWorkspace.js'; -import { ChatPillActionViewItem } from '../../../../workbench/browser/chatPills.js'; -import { SessionHasWorkspaceContext, IsQuickChatSessionContext } from '../../../common/contextkeys.js'; -import { NEW_FILE_TAB_COMMAND_ID } from '../../../common/sessionCommands.js'; -import { ISessionContext } from '../../../services/sessions/browser/sessionContext.js'; -import { ISessionsService } from '../../../services/sessions/browser/sessionsService.js'; -import { IActiveSession } from '../../../services/sessions/common/sessionsManagement.js'; -import { SESSIONS_FILES_VIEW_ID } from './filesView.js'; - -// --- Open Files view action - -export class OpenFilesViewAction extends Action2 { - static readonly ID = 'workbench.agentSessions.action.openFilesView'; - - constructor() { - super({ - id: OpenFilesViewAction.ID, - title: localize2('agentSessions.files', 'Files'), - icon: Codicon.folder, - f1: false, - // Workspace metadata pill, ordered before changes. - menu: { - id: Menus.SessionHeaderMeta, - group: 'navigation', - order: -10, - when: ContextKeyExpr.and( - SessionHasWorkspaceContext, - IsQuickChatSessionContext.negate(), - ) - }, - }); - } - - override async run(accessor: ServicesAccessor, session?: IActiveSession): Promise { - const sessionsService = accessor.get(ISessionsService); - const viewsService = accessor.get(IViewsService); - const commandService = accessor.get(ICommandService); - const layoutService = accessor.get(IAgentWorkbenchLayoutService); - - // The clicked pill forwards its session. Fall back to the active session - // when invoked without an explicit argument. - const targetSession = session ?? sessionsService.activeSession.get(); - if (!targetSession) { - return; - } - - if (layoutService.isSinglePaneLayoutEnabled) { - await commandService.executeCommand(NEW_FILE_TAB_COMMAND_ID); - } - - await viewsService.openView(SESSIONS_FILES_VIEW_ID, false); - } -} -registerAction2(OpenFilesViewAction); - -// --- Open Files view action view item - -/** - * Renders the session's workspace folder as a `