mirror of
https://github.com/microsoft/vscode.git
synced 2026-09-30 18:46:00 +01:00
504 lines
17 KiB
TypeScript
504 lines
17 KiB
TypeScript
/*---------------------------------------------------------------------------------------------
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
* Licensed under the MIT License. See License.txt in the project root for license information.
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
import { Codicon } from '../../../../base/common/codicons.js';
|
|
import { Disposable } from '../../../../base/common/lifecycle.js';
|
|
import { observableFromEvent } from '../../../../base/common/observable.js';
|
|
import { isEqual } from '../../../../base/common/resources.js';
|
|
import { URI } from '../../../../base/common/uri.js';
|
|
import { EditorContextKeys } from '../../../../editor/common/editorContextKeys.js';
|
|
import { localize, localize2 } from '../../../../nls.js';
|
|
import { Action2, IAction2Options, MenuId, MenuRegistry, registerAction2 } from '../../../../platform/actions/common/actions.js';
|
|
import { ICommandService } from '../../../../platform/commands/common/commands.js';
|
|
import { ContextKeyExpr, IContextKeyService } from '../../../../platform/contextkey/common/contextkey.js';
|
|
import { ServicesAccessor } from '../../../../platform/instantiation/common/instantiation.js';
|
|
import { bindContextKey } from '../../../../platform/observable/common/platformObservableUtils.js';
|
|
import { ITelemetryService } from '../../../../platform/telemetry/common/telemetry.js';
|
|
import { DIFF_VIEW_MODE_INLINE_TEMPORARY, SET_DIFF_VIEW_MODE_AUTOMATIC, SET_DIFF_VIEW_MODE_INLINE, SET_DIFF_VIEW_MODE_SIDE_BY_SIDE, TOGGLE_DIFF_SIDE_BY_SIDE } from '../../../../workbench/browser/parts/editor/diffEditorCommands.js';
|
|
import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js';
|
|
import { ActiveEditorContext, AuxiliaryBarVisibleContext, IsAuxiliaryWindowContext, IsSessionsWindowContext, IsTopRightEditorGroupContext, MainEditorAreaVisibleContext, TextCompareEditorActiveContext } from '../../../../workbench/common/contextkeys.js';
|
|
import { DiffEditorInput } from '../../../../workbench/common/editor/diffEditorInput.js';
|
|
import { IEditorService } from '../../../../workbench/services/editor/common/editorService.js';
|
|
import { IViewsService } from '../../../../workbench/services/views/common/viewsService.js';
|
|
import { MultiDiffEditor } from '../../../../workbench/contrib/multiDiffEditor/browser/multiDiffEditor.js';
|
|
import { Menus } from '../../../browser/menus.js';
|
|
import { CustomViewVisibleContext, SessionHasChangesContext, SessionIsCreatedContext, SinglePaneDiffEditorInputActiveContext, SinglePaneLayoutEnabledContext } from '../../../common/contextkeys.js';
|
|
import { logChangesViewViewModeChange } from '../../../common/sessionsTelemetry.js';
|
|
import { ISessionsService } from '../../../services/sessions/browser/sessionsService.js';
|
|
import { OPEN_PULL_REQUEST_ACTION_ID } from '../../github/common/types.js';
|
|
import { ActiveSessionContextKeys, CHANGES_VIEW_ID, ChangesContextKeys, ChangesViewMode, SESSIONS_CHANGES_OPEN_SINGLE_FILE_DIFF_SETTING } from '../common/changes.js';
|
|
import { IChangesViewService } from '../common/changesViewService.js';
|
|
import { SessionsDiffViewModeContext } from '../../editor/common/diffEditorOptionsService.js';
|
|
import { CHANGES_HEADER_ACTIONS_ID } from './changesView.js';
|
|
import { SessionChangesEditor } from './sessionChangesEditor.js';
|
|
|
|
const openChangesViewActionOptions: IAction2Options = {
|
|
id: 'workbench.action.agentSessions.openChangesView',
|
|
title: localize2('openChangesView', "Changes"),
|
|
icon: Codicon.diffMultiple,
|
|
f1: false,
|
|
};
|
|
|
|
class OpenChangesViewAction extends Action2 {
|
|
|
|
static readonly ID = openChangesViewActionOptions.id;
|
|
|
|
constructor() {
|
|
super(openChangesViewActionOptions);
|
|
}
|
|
|
|
async run(accessor: ServicesAccessor): Promise<void> {
|
|
const viewsService = accessor.get(IViewsService);
|
|
await viewsService.openView(CHANGES_VIEW_ID, true);
|
|
}
|
|
}
|
|
|
|
registerAction2(OpenChangesViewAction);
|
|
|
|
class ChangesViewActionsContribution extends Disposable implements IWorkbenchContribution {
|
|
|
|
static readonly ID = 'workbench.contrib.changesViewActions';
|
|
|
|
constructor(
|
|
@IContextKeyService contextKeyService: IContextKeyService,
|
|
@ISessionsService sessionsService: ISessionsService,
|
|
@IChangesViewService changesViewService: IChangesViewService,
|
|
@IEditorService editorService: IEditorService,
|
|
) {
|
|
super();
|
|
|
|
// Bind context key: true when the active session has changes
|
|
this._register(bindContextKey(ActiveSessionContextKeys.HasChanges, contextKeyService, reader => {
|
|
const activeSession = sessionsService.activeSession.read(reader);
|
|
if (!activeSession) {
|
|
return false;
|
|
}
|
|
const changes = activeSession.changes.read(reader);
|
|
return changes.length > 0;
|
|
}));
|
|
|
|
this._register(bindContextKey(ChangesContextKeys.ViewMode, contextKeyService, reader => {
|
|
return changesViewService.viewModeObs.read(reader);
|
|
}));
|
|
|
|
const activeEditor = observableFromEvent(this, editorService.onDidActiveEditorChange, () => editorService.activeEditor);
|
|
this._register(bindContextKey(SinglePaneDiffEditorInputActiveContext, contextKeyService, reader => activeEditor.read(reader) instanceof DiffEditorInput));
|
|
}
|
|
}
|
|
|
|
registerWorkbenchContribution2(ChangesViewActionsContribution.ID, ChangesViewActionsContribution, WorkbenchPhase.AfterRestored);
|
|
|
|
class OpenPullRequestAction extends Action2 {
|
|
static readonly ID = 'workbench.action.agentSessions.openPullRequest';
|
|
|
|
constructor() {
|
|
super({
|
|
id: OpenPullRequestAction.ID,
|
|
title: localize2('openPullRequest', "Open Pull Request"),
|
|
icon: Codicon.gitPullRequest,
|
|
f1: false,
|
|
menu: {
|
|
id: MenuId.AgentsChangesToolbar,
|
|
group: 'navigation',
|
|
order: 9,
|
|
when: ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
ActiveSessionContextKeys.HasPullRequest)
|
|
}
|
|
});
|
|
}
|
|
|
|
async run(accessor: ServicesAccessor): Promise<void> {
|
|
const commandService = accessor.get(ICommandService);
|
|
const sessionsService = accessor.get(ISessionsService);
|
|
const activeSession = sessionsService.activeSession.get();
|
|
if (!activeSession) {
|
|
return;
|
|
}
|
|
|
|
await commandService.executeCommand(OPEN_PULL_REQUEST_ACTION_ID, activeSession);
|
|
}
|
|
}
|
|
|
|
registerAction2(OpenPullRequestAction);
|
|
|
|
const agentsChangesEditorActive = ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
ActiveEditorContext.isEqualTo(SessionChangesEditor.ID)
|
|
);
|
|
|
|
const singlePaneFileDiffEditorActive = ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
SinglePaneDiffEditorInputActiveContext,
|
|
SinglePaneLayoutEnabledContext
|
|
);
|
|
|
|
const agentsTextDiffEditorActive = ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
TextCompareEditorActiveContext
|
|
);
|
|
|
|
const agentsMultiDiffEditorActive = ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
ActiveEditorContext.isEqualTo(MultiDiffEditor.ID)
|
|
);
|
|
|
|
const agentsDiffEditorActive = ContextKeyExpr.or(
|
|
agentsChangesEditorActive,
|
|
agentsTextDiffEditorActive,
|
|
agentsMultiDiffEditorActive
|
|
);
|
|
|
|
const singlePaneChangesEditorActive = ContextKeyExpr.and(agentsChangesEditorActive, SinglePaneLayoutEnabledContext);
|
|
const singlePaneTextDiffEditorActive = ContextKeyExpr.and(agentsTextDiffEditorActive, SinglePaneLayoutEnabledContext);
|
|
|
|
// Title-bar (tab-row) gate that does NOT require the editor content area to be
|
|
// visible, so session-level title actions (e.g. Create Pull Request) stay available
|
|
// when the editor area is closed but the docked tab bar is still shown.
|
|
const singlePaneChangesEditorTitle = ContextKeyExpr.and(
|
|
singlePaneChangesEditorActive,
|
|
IsAuxiliaryWindowContext.toNegated(),
|
|
IsTopRightEditorGroupContext
|
|
);
|
|
|
|
const singlePaneChangesEditorTitleVisible = ContextKeyExpr.and(
|
|
singlePaneChangesEditorTitle,
|
|
MainEditorAreaVisibleContext
|
|
);
|
|
|
|
const singlePaneDiffEditorTitle = ContextKeyExpr.and(
|
|
ContextKeyExpr.or(singlePaneChangesEditorActive, singlePaneFileDiffEditorActive),
|
|
IsAuxiliaryWindowContext.toNegated(),
|
|
IsTopRightEditorGroupContext
|
|
);
|
|
|
|
const singlePaneTextDiffEditorTitle = ContextKeyExpr.and(
|
|
singlePaneTextDiffEditorActive,
|
|
IsAuxiliaryWindowContext.toNegated(),
|
|
IsTopRightEditorGroupContext
|
|
);
|
|
|
|
const singlePaneMultiDiffEditorTitle = ContextKeyExpr.and(
|
|
agentsMultiDiffEditorActive,
|
|
SinglePaneLayoutEnabledContext,
|
|
IsAuxiliaryWindowContext.toNegated(),
|
|
IsTopRightEditorGroupContext
|
|
);
|
|
|
|
const singlePaneDiffEditorTitleVisible = ContextKeyExpr.and(
|
|
ContextKeyExpr.or(
|
|
singlePaneChangesEditorTitle,
|
|
singlePaneTextDiffEditorTitle,
|
|
singlePaneMultiDiffEditorTitle
|
|
),
|
|
MainEditorAreaVisibleContext
|
|
);
|
|
|
|
/** Anchor action hosting the Create Pull Request button bar in the title bar. */
|
|
class ChangesHeaderActionsAction extends Action2 {
|
|
constructor() {
|
|
super({
|
|
id: CHANGES_HEADER_ACTIONS_ID,
|
|
title: localize2('changesView.headerActions', "Changes Actions"),
|
|
f1: false,
|
|
menu: {
|
|
id: Menus.TitleBarSessionMenu,
|
|
group: 'navigation',
|
|
order: 5,
|
|
when: ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
IsAuxiliaryWindowContext.toNegated(),
|
|
CustomViewVisibleContext.negate(),
|
|
SinglePaneLayoutEnabledContext,
|
|
SessionIsCreatedContext,
|
|
SessionHasChangesContext
|
|
)
|
|
},
|
|
});
|
|
}
|
|
override async run(): Promise<void> { }
|
|
}
|
|
|
|
registerAction2(ChangesHeaderActionsAction);
|
|
|
|
class SetChangesListViewModeAction extends Action2 {
|
|
static readonly ID = 'workbench.action.agentSessions.setChangesListViewMode';
|
|
|
|
constructor() {
|
|
super({
|
|
id: SetChangesListViewModeAction.ID,
|
|
title: localize2('agentSessions.setChangesListViewMode', "View as List"),
|
|
icon: Codicon.listFlat,
|
|
f1: false,
|
|
menu: {
|
|
id: Menus.SessionsEditorHeaderLayout,
|
|
group: 'secondary/2_viewMode',
|
|
order: 20,
|
|
when: ContextKeyExpr.and(
|
|
singlePaneDiffEditorTitle,
|
|
AuxiliaryBarVisibleContext,
|
|
ChangesContextKeys.ViewMode.isEqualTo(ChangesViewMode.Tree))
|
|
}
|
|
});
|
|
}
|
|
|
|
run(accessor: ServicesAccessor): void {
|
|
logChangesViewViewModeChange(accessor.get(ITelemetryService), ChangesViewMode.List);
|
|
accessor.get(IChangesViewService).setViewMode(ChangesViewMode.List);
|
|
}
|
|
}
|
|
|
|
registerAction2(SetChangesListViewModeAction);
|
|
|
|
class SetChangesTreeViewModeAction extends Action2 {
|
|
static readonly ID = 'workbench.action.agentSessions.setChangesTreeViewMode';
|
|
|
|
constructor() {
|
|
super({
|
|
id: SetChangesTreeViewModeAction.ID,
|
|
title: localize2('agentSessions.setChangesTreeViewMode', "View as Tree"),
|
|
icon: Codicon.listTree,
|
|
f1: false,
|
|
menu: {
|
|
id: Menus.SessionsEditorHeaderLayout,
|
|
group: 'secondary/2_viewMode',
|
|
order: 20,
|
|
when: ContextKeyExpr.and(
|
|
singlePaneDiffEditorTitle,
|
|
AuxiliaryBarVisibleContext,
|
|
ChangesContextKeys.ViewMode.isEqualTo(ChangesViewMode.List))
|
|
}
|
|
});
|
|
}
|
|
|
|
run(accessor: ServicesAccessor): void {
|
|
logChangesViewViewModeChange(accessor.get(ITelemetryService), ChangesViewMode.Tree);
|
|
accessor.get(IChangesViewService).setViewMode(ChangesViewMode.Tree);
|
|
}
|
|
}
|
|
|
|
registerAction2(SetChangesTreeViewModeAction);
|
|
|
|
class CollapseAllSessionChangesDiffsAction extends Action2 {
|
|
static readonly ID = 'workbench.action.agentSessions.collapseAllDiffs';
|
|
|
|
constructor() {
|
|
super({
|
|
id: CollapseAllSessionChangesDiffsAction.ID,
|
|
title: localize2('agentSessions.collapseAllDiffs', "Collapse All Diffs"),
|
|
icon: Codicon.collapseAll,
|
|
f1: false,
|
|
menu: {
|
|
id: Menus.SessionsEditorHeaderLayout,
|
|
group: 'secondary/1_diff',
|
|
order: 10,
|
|
when: ContextKeyExpr.and(
|
|
singlePaneChangesEditorTitleVisible,
|
|
ContextKeyExpr.not('multiDiffEditorAllCollapsed'))
|
|
}
|
|
});
|
|
}
|
|
|
|
run(accessor: ServicesAccessor): void {
|
|
const activeEditorPane = accessor.get(IEditorService).activeEditorPane;
|
|
if (activeEditorPane instanceof SessionChangesEditor) {
|
|
activeEditorPane.collapseAllDiffs();
|
|
}
|
|
}
|
|
}
|
|
|
|
registerAction2(CollapseAllSessionChangesDiffsAction);
|
|
|
|
class ExpandAllSessionChangesDiffsAction extends Action2 {
|
|
static readonly ID = 'workbench.action.agentSessions.expandAllDiffs';
|
|
|
|
constructor() {
|
|
super({
|
|
id: ExpandAllSessionChangesDiffsAction.ID,
|
|
title: localize2('agentSessions.expandAllDiffs', "Expand All Diffs"),
|
|
icon: Codicon.expandAll,
|
|
f1: false,
|
|
menu: {
|
|
id: Menus.SessionsEditorHeaderLayout,
|
|
group: 'secondary/1_diff',
|
|
order: 10,
|
|
when: ContextKeyExpr.and(
|
|
singlePaneChangesEditorActive,
|
|
IsAuxiliaryWindowContext.toNegated(),
|
|
IsTopRightEditorGroupContext,
|
|
MainEditorAreaVisibleContext,
|
|
ContextKeyExpr.has('multiDiffEditorAllCollapsed'))
|
|
}
|
|
});
|
|
}
|
|
|
|
run(accessor: ServicesAccessor): void {
|
|
const activeEditorPane = accessor.get(IEditorService).activeEditorPane;
|
|
if (activeEditorPane instanceof SessionChangesEditor) {
|
|
activeEditorPane.expandAllDiffs();
|
|
}
|
|
}
|
|
}
|
|
|
|
registerAction2(ExpandAllSessionChangesDiffsAction);
|
|
|
|
// The Agents window reuses the workbench `toggle.diff.renderSideBySide` command so a
|
|
// user's keybinding for it carries over here (issue #324765). The sessions override of
|
|
// IDiffEditorCommandsService updates the Changes editor's own preferred layout.
|
|
|
|
MenuRegistry.appendMenuItem(Menus.SessionsEditorTitle, {
|
|
submenu: Menus.SessionsDiffEditorView,
|
|
title: localize('diffView', "Diff View"),
|
|
group: '1_diff',
|
|
order: 10,
|
|
when: singlePaneDiffEditorTitleVisible,
|
|
});
|
|
MenuRegistry.appendMenuItem(MenuId.EditorTitle, {
|
|
submenu: Menus.SessionsDiffEditorView,
|
|
title: localize('diffView', "Diff View"),
|
|
group: '1_diff',
|
|
order: 10,
|
|
when: ContextKeyExpr.and(agentsDiffEditorActive, SinglePaneLayoutEnabledContext.negate()),
|
|
});
|
|
MenuRegistry.appendMenuItem(Menus.SessionsDiffEditorView, {
|
|
command: {
|
|
id: SET_DIFF_VIEW_MODE_INLINE,
|
|
title: localize('diffView.inline', "Inline"),
|
|
toggled: SessionsDiffViewModeContext.isEqualTo('inline'),
|
|
},
|
|
group: '1_view',
|
|
order: 1,
|
|
});
|
|
MenuRegistry.appendMenuItem(Menus.SessionsDiffEditorView, {
|
|
command: {
|
|
id: SET_DIFF_VIEW_MODE_SIDE_BY_SIDE,
|
|
title: localize('diffView.sideBySide', "Side by Side"),
|
|
toggled: SessionsDiffViewModeContext.isEqualTo('sideBySide'),
|
|
},
|
|
group: '1_view',
|
|
order: 2,
|
|
});
|
|
for (const [title, when] of [
|
|
[localize('diffView.automaticSideBySide', "Automatic (Currently Side by Side)"), EditorContextKeys.diffEditorAutomaticRenderSideBySide],
|
|
[localize('diffView.automaticInline', "Automatic (Currently Inline)"), EditorContextKeys.diffEditorAutomaticRenderSideBySide.toNegated()],
|
|
] as const) {
|
|
MenuRegistry.appendMenuItem(Menus.SessionsDiffEditorView, {
|
|
command: {
|
|
id: SET_DIFF_VIEW_MODE_AUTOMATIC,
|
|
title,
|
|
toggled: ContextKeyExpr.and(
|
|
SessionsDiffViewModeContext.isEqualTo('automatic'),
|
|
EditorContextKeys.diffEditorTemporaryInlineMode.toNegated(),
|
|
),
|
|
},
|
|
group: '1_view',
|
|
order: 3,
|
|
when,
|
|
});
|
|
}
|
|
MenuRegistry.appendMenuItem(Menus.SessionsDiffEditorView, {
|
|
command: {
|
|
id: DIFF_VIEW_MODE_INLINE_TEMPORARY,
|
|
title: localize('diffView.inlineTemporary', "Inline (Temporary)"),
|
|
toggled: EditorContextKeys.diffEditorTemporaryInlineMode,
|
|
precondition: ContextKeyExpr.false(),
|
|
},
|
|
group: '1_view',
|
|
order: 4,
|
|
when: EditorContextKeys.diffEditorTemporaryInlineMode,
|
|
});
|
|
|
|
// Discoverable in the command palette while a Changes diff editor is visible.
|
|
MenuRegistry.appendMenuItem(MenuId.CommandPalette, {
|
|
command: {
|
|
id: TOGGLE_DIFF_SIDE_BY_SIDE,
|
|
title: localize2('togglePreferredDiffView', "Toggle Preferred Diff View"),
|
|
category: localize2('changes', "Changes"),
|
|
},
|
|
when: agentsDiffEditorActive
|
|
});
|
|
|
|
class OpenChangesAction extends Action2 {
|
|
static readonly ID = 'workbench.action.agentSessions.openChanges';
|
|
|
|
constructor() {
|
|
super({
|
|
id: OpenChangesAction.ID,
|
|
title: localize2('openChanges', "Open Changes"),
|
|
icon: Codicon.gitCompare,
|
|
f1: false
|
|
});
|
|
}
|
|
|
|
async run(accessor: ServicesAccessor, _sessionResource: URI, _ref: string, ...resources: URI[]): Promise<void> {
|
|
const editorService = accessor.get(IEditorService);
|
|
const changesViewService = accessor.get(IChangesViewService);
|
|
|
|
const sessionChanges = changesViewService.activeSessionChangesObs.get();
|
|
|
|
const changes = sessionChanges?.filter(change =>
|
|
resources.some(resource => isEqual(change.modifiedUri ?? change.originalUri, resource))
|
|
) ?? [];
|
|
|
|
await Promise.all(changes.map(change => editorService.openEditor({
|
|
original: { resource: change.originalUri },
|
|
modified: { resource: change.modifiedUri }
|
|
})));
|
|
}
|
|
}
|
|
|
|
registerAction2(OpenChangesAction);
|
|
|
|
const openSingleFileDiffEnabled = ContextKeyExpr.equals(`config.${SESSIONS_CHANGES_OPEN_SINGLE_FILE_DIFF_SETTING}`, true);
|
|
|
|
class OpenFileAction extends Action2 {
|
|
static readonly ID = 'workbench.action.agentSessions.openFile';
|
|
|
|
constructor() {
|
|
super({
|
|
id: OpenFileAction.ID,
|
|
title: localize2('openFile', "Open File"),
|
|
icon: Codicon.goToFile,
|
|
f1: false,
|
|
menu: [
|
|
// When opening a file already shows a single file diff, the "Open
|
|
// Changes" alt action is redundant and is therefore omitted.
|
|
{
|
|
id: MenuId.AgentsChangeInlineToolbar,
|
|
group: 'navigation',
|
|
order: 1,
|
|
when: ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
ChangesContextKeys.ChangeKind.isEqualTo('file'),
|
|
openSingleFileDiffEnabled)
|
|
},
|
|
// Default behavior: the alt action ("Open Changes") opens a diff
|
|
// editor for the selected change(s).
|
|
{
|
|
id: MenuId.AgentsChangeInlineToolbar,
|
|
group: 'navigation',
|
|
order: 1,
|
|
alt: {
|
|
id: OpenChangesAction.ID,
|
|
title: localize2('openChanges', "Open Changes"),
|
|
icon: Codicon.gitCompare,
|
|
},
|
|
when: ContextKeyExpr.and(
|
|
IsSessionsWindowContext,
|
|
ChangesContextKeys.ChangeKind.isEqualTo('file'),
|
|
openSingleFileDiffEnabled.negate())
|
|
}
|
|
]
|
|
});
|
|
}
|
|
|
|
async run(accessor: ServicesAccessor, _sessionResource: URI, _ref: string, ...resources: URI[]): Promise<void> {
|
|
const editorService = accessor.get(IEditorService);
|
|
await Promise.all(resources.map(resource => editorService.openEditor({ resource })));
|
|
}
|
|
}
|
|
|
|
registerAction2(OpenFileAction);
|