Skip to content

Commit 92da29e

Browse files
Copilotrebornix
andauthored
Add Ctrl/Cmd+click support to open PR changes and session logs in second editor group (#7139)
* Initial plan * Implement Ctrl/Cmd+click functionality for second editor group Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> * Add tests and documentation for Ctrl/Cmd+click functionality Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> * Add Ctrl/Cmd+click support to open PR changes and session logs in second editor group Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> * rename args * remove tests. * improve view group handling --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: rebornix <876920+rebornix@users.noreply.github.com> Co-authored-by: Peng Lyu <penn.lv@gmail.com>
1 parent c05580f commit 92da29e

7 files changed

Lines changed: 46 additions & 17 deletions

File tree

src/github/copilotApi.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import fetch from 'cross-fetch';
77
import JSZip from 'jszip';
88
import * as vscode from 'vscode';
99
import { AuthProvider } from '../common/authentication';
10+
import Logger from '../common/logger';
1011
import { Remote } from '../common/remote';
1112
import { OctokitCommon } from './common';
1213
import { CredentialStore } from './credentials';
1314
import { LoggingOctokit } from './loggingOctokit';
1415
import { hasEnterpriseUri } from './utils';
15-
import Logger from '../common/logger';
1616

1717
const LEARN_MORE_URL = 'https://docs.github.com/en/copilot/how-tos/agents/copilot-coding-agent';
1818
const PREMIUM_REQUESTS_URL = 'https://docs.github.com/en/copilot/concepts/copilot-billing/understanding-and-managing-requests-in-copilot#what-are-premium-requests';

src/github/pullRequestModel.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1274,7 +1274,7 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
12741274
return this.githubRepository.getStatusChecks(this.number);
12751275
}
12761276

1277-
static async openChanges(folderManager: FolderRepositoryManager, pullRequestModel: PullRequestModel): Promise<void> {
1277+
static async openChanges(folderManager: FolderRepositoryManager, pullRequestModel: PullRequestModel, openToTheSide?: boolean): Promise<void> {
12781278
const changeModels = await PullRequestModel.getChangeModels(folderManager, pullRequestModel);
12791279
const args: [vscode.Uri, vscode.Uri | undefined, vscode.Uri | undefined][] = [];
12801280
for (const changeModel of changeModels) {
@@ -1285,6 +1285,15 @@ export class PullRequestModel extends IssueModel<PullRequest> implements IPullRe
12851285
"pr.openChanges" : {}
12861286
*/
12871287
folderManager.telemetry.sendTelemetryEvent('pr.openChanges');
1288+
1289+
if (openToTheSide) {
1290+
if (vscode.window.tabGroups.all.length < 2) {
1291+
await vscode.commands.executeCommand('workbench.action.splitEditor');
1292+
}
1293+
1294+
await vscode.commands.executeCommand('workbench.action.focusSecondEditorGroup');
1295+
}
1296+
12881297
return vscode.commands.executeCommand('vscode.changes', vscode.l10n.t('Changes in Pull Request #{0}', pullRequestModel.number), args);
12891298
}
12901299

src/github/pullRequestOverview.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -361,7 +361,7 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
361361
case 'pr.open-diff':
362362
return this.openDiff(message);
363363
case 'pr.open-changes':
364-
return this.openChanges();
364+
return this.openChanges(message);
365365
case 'pr.resolve-comment-thread':
366366
return this.resolveCommentThread(message);
367367
case 'pr.checkMergeability':
@@ -481,9 +481,10 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
481481
}
482482
}
483483

484-
private async openSessionLog(message: IRequestMessage<{ link: SessionLinkInfo }>): Promise<void> {
484+
private async openSessionLog(message: IRequestMessage<{ link: SessionLinkInfo; openToTheSide?: boolean }>): Promise<void> {
485485
try {
486-
await SessionLogViewManager.instance?.openForPull(this._item, message.args.link);
486+
const openToTheSide = message.args.openToTheSide || false;
487+
await SessionLogViewManager.instance?.openForPull(this._item, message.args.link, openToTheSide);
487488
} catch (e) {
488489
Logger.error(`Open session log view failed: ${formatError(e)}`, PullRequestOverviewPanel.ID);
489490
}
@@ -524,8 +525,9 @@ export class PullRequestOverviewPanel extends IssueOverviewPanel<PullRequestMode
524525
}
525526
}
526527

527-
private async openChanges(): Promise<void> {
528-
return PullRequestModel.openChanges(this._folderRepositoryManager, this._item);
528+
private async openChanges(message?: IRequestMessage<{ openToTheSide?: boolean }>): Promise<void> {
529+
const openToTheSide = message?.args?.openToTheSide || false;
530+
return PullRequestModel.openChanges(this._folderRepositoryManager, this._item, openToTheSide);
529531
}
530532

531533
private async resolveCommentThread(message: IRequestMessage<{ threadId: string, toResolve: boolean, thread: IComment[] }>) {

src/view/sessionLogView.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ export class SessionLogViewManager extends Disposable implements vscode.WebviewP
6969

7070
const sessionLogs = await copilotAgentManager.getSessionLogsFromSessionId(picked.sessionId);
7171

72-
return this.open(sessionLogs, undefined);
72+
return this.open(sessionLogs, undefined, false);
7373
}));
7474
}
7575

@@ -83,7 +83,7 @@ export class SessionLogViewManager extends Disposable implements vscode.WebviewP
8383
super.dispose();
8484
}
8585

86-
async openForPull(pullRequest: PullRequestModel, link: SessionLinkInfo): Promise<void> {
86+
async openForPull(pullRequest: PullRequestModel, link: SessionLinkInfo, openToTheSide?: boolean): Promise<void> {
8787
try {
8888
// TODO: We should not block opening the webview here. When does this actually fail?
8989

@@ -101,7 +101,7 @@ export class SessionLogViewManager extends Disposable implements vscode.WebviewP
101101
existingPanel.revealAndRefresh(sessionLogs);
102102
return;
103103
} else {
104-
return this.open(sessionLogs, pullRequest);
104+
return this.open(sessionLogs, pullRequest, openToTheSide);
105105
}
106106
} catch (error) {
107107
Logger.error(`Failed to retrieve session logs: ${error}`, 'SessionLogViewManager');
@@ -118,16 +118,17 @@ export class SessionLogViewManager extends Disposable implements vscode.WebviewP
118118
return Array.from(this._panels).find(panel => panel.view.isForPullRequest(pullRequest))?.view;
119119
}
120120

121-
async open(logs: IAPISessionLogs, pullRequest: PullRequestModel | undefined): Promise<void> {
121+
async open(logs: IAPISessionLogs, pullRequest: PullRequestModel | undefined, openToTheSide?: boolean): Promise<void> {
122122
const copilotApi = await getCopilotApi(this.credentialStore);
123123
if (!copilotApi) {
124124
return;
125125
}
126126

127+
const viewColumn = openToTheSide ? vscode.ViewColumn.Beside : vscode.ViewColumn.Active;
127128
const webviewPanel = vscode.window.createWebviewPanel(
128129
SessionLogViewManager.viewType,
129130
pullRequest ? vscode.l10n.t(`Session Log (Pull #{0})`, pullRequest.number) : vscode.l10n.t('Session Log'),
130-
vscode.ViewColumn.Active,
131+
viewColumn,
131132
{
132133
retainContextWhenHidden: true,
133134
enableFindWidget: true

webviews/common/context.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ export class PRContext {
3333

3434
public checkout = () => this.postMessage({ command: 'pr.checkout' });
3535

36-
public openChanges = () => this.postMessage({ command: 'pr.open-changes' });
36+
public openChanges = (openToTheSide?: boolean) => this.postMessage({ command: 'pr.open-changes', args: { openToTheSide } });
3737

3838
public copyPrLink = () => this.postMessage({ command: 'pr.copy-prlink' });
3939

@@ -258,7 +258,7 @@ export class PRContext {
258258
});
259259
};
260260

261-
public openSessionLog = (link: SessionLinkInfo) => this.postMessage({ command: 'pr.open-session-log', args: { link } });
261+
public openSessionLog = (link: SessionLinkInfo, openToTheSide?: boolean) => this.postMessage({ command: 'pr.open-session-log', args: { link, openToTheSide } });
262262

263263
setPR = (pr: PullRequest) => {
264264
this.pr = pr;

webviews/components/header.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,11 +105,16 @@ function Title({ title, titleHTML, number, url, inEditMode, setEditMode, setCurr
105105
function ButtonGroup({ isCurrentlyCheckedOut, canEdit, isIssue, repositoryDefaultBranch, setEditMode }) {
106106
const { refresh, copyPrLink, copyVscodeDevLink, openChanges } = useContext(PullRequestContext);
107107

108+
const handleOpenChangesClick = (e: React.MouseEvent) => {
109+
const openToTheSide = e.ctrlKey || e.metaKey;
110+
openChanges(openToTheSide);
111+
};
112+
108113
return (
109114
<div className="button-group">
110115
<CheckoutButtons {...{ isCurrentlyCheckedOut, isIssue, repositoryDefaultBranch }} />
111116
{!isIssue && (
112-
<button title="Open Changes" onClick={openChanges} className="small-button">
117+
<button title="Open Changes (Ctrl/Cmd+Click to open in second editor group)" onClick={handleOpenChangesClick} className="small-button">
113118
Open Changes
114119
</button>
115120
)}

webviews/components/timeline.tsx

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,6 +440,13 @@ const CopilotStartedEventView = (event: CopilotStartedEvent) => {
440440
const { createdAt, onBehalfOf, sessionLink } = event;
441441
const { openSessionLog } = useContext(PullRequestContext);
442442

443+
const handleSessionLogClick = (e: React.MouseEvent) => {
444+
if (sessionLink) {
445+
const openToTheSide = e.ctrlKey || e.metaKey;
446+
openSessionLog(sessionLink, openToTheSide);
447+
}
448+
};
449+
443450
return (
444451
<div className="comment-container commit">
445452
<div className="commit-message">
@@ -449,7 +456,7 @@ const CopilotStartedEventView = (event: CopilotStartedEvent) => {
449456
</div>
450457
{sessionLink ? (
451458
<div className="timeline-detail">
452-
<a onClick={() => openSessionLog(sessionLink)}><button className='secondary'>View session</button></a>
459+
<a onClick={handleSessionLogClick}><button className='secondary' title="View session log (Ctrl/Cmd+Click to open in second editor group)">View session</button></a>
453460
</div>)
454461
: null}
455462
<Timestamp date={createdAt} />
@@ -475,6 +482,11 @@ const CopilotFinishedErrorEventView = (event: CopilotFinishedErrorEvent) => {
475482
const { createdAt, onBehalfOf } = event;
476483
const { openSessionLog } = useContext(PullRequestContext);
477484

485+
const handleSessionLogClick = (e: React.MouseEvent) => {
486+
const openToTheSide = e.ctrlKey || e.metaKey;
487+
openSessionLog(event.sessionLink, openToTheSide);
488+
};
489+
478490
return (
479491
<div className="comment-container commit">
480492
<div className='timeline-with-detail'>
@@ -484,7 +496,7 @@ const CopilotFinishedErrorEventView = (event: CopilotFinishedErrorEvent) => {
484496
<div className="message">Copilot stopped work on behalf of <AuthorLink for={onBehalfOf} /> due to an error</div>
485497
</div>
486498
<div className="commit-message-detail">
487-
<a onClick={() => openSessionLog(event.sessionLink)}>Copilot has encountered an error. See logs for additional details.</a>
499+
<a onClick={handleSessionLogClick} title="View session log (Ctrl/Cmd+Click to open in second editor group)">Copilot has encountered an error. See logs for additional details.</a>
488500
</div>
489501
</div>
490502
<Timestamp date={createdAt} />

0 commit comments

Comments
 (0)