Skip to content

Commit bf8f6ca

Browse files
authored
fix: open previous/next revision (conwnet#751)
1 parent 9701b5f commit bf8f6ca

13 files changed

Lines changed: 191 additions & 162 deletions

File tree

‎extensions/github1s/package-lock.json‎

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎extensions/github1s/package.json‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
],
2626
"browser": "./dist/extension",
2727
"engines": {
28-
"vscode": "^1.48.0"
28+
"vscode": "^1.96.0"
2929
},
3030
"contributes": {
3131
"resourceLabelFormatters": [
@@ -244,7 +244,7 @@
244244
"dark": "assets/icons/dark/open-left-file.svg",
245245
"light": "assets/icons/light/open-left-file.svg"
246246
},
247-
"enablement": "isInDiffEditor && resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/ && resource =~ /^(?![^?]*\\?[^#]*(%26|\\b)base(=|%3D|%3d)github1s-empty-file)/"
247+
"enablement": "isInDiffEditor && diffEditorOriginalUri && !(diffEditorOriginalUri =~ /^github1s-empty-file:/)"
248248
},
249249
{
250250
"command": "github1s.commands.diffViewOpenRightFile",
@@ -254,21 +254,21 @@
254254
"dark": "assets/icons/dark/open-right-file.svg",
255255
"light": "assets/icons/light/open-right-file.svg"
256256
},
257-
"enablement": "isInDiffEditor && resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/ && resource =~ /^(?![^?]*\\?[^#]*(%26|\\b)head(=|%3D|%3d)github1s-empty-file)/"
257+
"enablement": "isInDiffEditor && diffEditorModifiedUri && !(diffEditorModifiedUri =~ /^github1s-empty-file:/)"
258258
},
259259
{
260260
"command": "github1s.commands.openFilePreviousRevision",
261261
"title": "Open Previous Revision",
262262
"category": "GitHub1s",
263263
"icon": "$(arrow-left)",
264-
"enablement": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ && resource =~ /^(?![^?]*\\?[^#]*(%26|\\b)base(=|%3D|%3d)github1s-empty-file)/"
264+
"enablement": "(!isInDiffEditor && resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/) || (isInDiffEditor && diffEditorOriginalUri =~ /^(github1s|gitlab1s|bitbucket1s):/)"
265265
},
266266
{
267267
"command": "github1s.commands.openFileNextRevision",
268268
"title": "Open Next Revision",
269269
"category": "GitHub1s",
270270
"icon": "$(arrow-right)",
271-
"enablement": "resource =~ /^[^?]*\\?[^#]*(%26|\\b)hasNextRevision(=|%3D|%3d)true/"
271+
"enablement": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ && resource =~ /^[^?]*\\?([^#]*(&|%26))?hasNext(=|%3D|%3d)true/"
272272
},
273273
{
274274
"command": "github1s.commands.toggleEditorGutterBlame",
@@ -588,12 +588,12 @@
588588
},
589589
{
590590
"command": "github1s.commands.openFilePreviousRevision",
591-
"when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/",
591+
"when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ || (isInDiffEditor && diffEditorOriginalUri =~ /^(github1s|gitlab1s|bitbucket1s):/)",
592592
"group": "navigation@4"
593593
},
594594
{
595595
"command": "github1s.commands.openFileNextRevision",
596-
"when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)/",
596+
"when": "resourceScheme =~ /^(github1s|gitlab1s|bitbucket1s)$/ || (isInDiffEditor && diffEditorOriginalUri =~ /^(github1s|gitlab1s|bitbucket1s):/)",
597597
"group": "navigation@5"
598598
},
599599
{

‎extensions/github1s/src/adapters/github1s/data-source.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,7 +281,7 @@ export class GitHub1sDataSource extends DataSource {
281281
page: options?.page,
282282
per_page: options?.pageSize,
283283
sha: options?.from,
284-
path: isString(options?.path) ? trimStart(options.path, '/') : undefined,
284+
path: trimStart(options?.path || '', '/') || undefined,
285285
author: options?.author,
286286
};
287287
const requestParams = { owner, repo, ...queryParams };

‎extensions/github1s/src/adapters/gitlab1s/data-source.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,7 @@ export class GitLab1sDataSource extends DataSource {
251251
page: options?.page,
252252
per_page: options?.pageSize,
253253
sha: options?.from,
254-
path: isString(options?.path) ? trimStart(options.path, '/') : undefined,
254+
path: trimStart(options?.path || '', '/') || undefined,
255255
author: options?.author,
256256
};
257257
const requestParams = { repo, ...queryParams };

‎extensions/github1s/src/adapters/sourcegraph/data-source.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -191,7 +191,7 @@ export class SourcegraphDataSource extends DataSource {
191191
let commits = await getCommits(
192192
this.buildRepository(repo),
193193
options?.from || 'HEAD',
194-
options?.path === undefined ? undefined : trimStart(options.path, '/'),
194+
trimStart(options?.path || '', '/') || undefined,
195195
options?.pageSize ? options.pageSize * (options.page || 1) : undefined,
196196
);
197197
if (options?.path && options.path !== '/' && commits.length) {

‎extensions/github1s/src/changes/files.ts‎

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
*/
55

66
import * as vscode from 'vscode';
7-
import queryString from 'query-string';
87
import * as adapterTypes from '@/adapters/types';
98
import router from '@/router';
109
import { basename } from '@/helpers/util';
@@ -117,15 +116,10 @@ export const getChangedFileDiffCommand = (changedFile: VSCodeChangedFile): vscod
117116
}
118117

119118
const title = getChangedFileDiffTitle(baseFileUri, headFileUri, status);
120-
const query = queryString.stringify({
121-
status,
122-
base: baseFileUri.with({ query: '' }).toString(),
123-
head: headFileUri.with({ query: '' }).toString(),
124-
});
125119

126120
return {
127121
title: 'Diff',
128122
command: 'vscode.diff',
129-
arguments: [baseFileUri.with({ query }), headFileUri.with({ query }), title],
123+
arguments: [baseFileUri, headFileUri, title],
130124
};
131125
};

‎extensions/github1s/src/changes/history.ts‎

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -95,21 +95,12 @@ export class GitHub1sHistoryProvider implements vscode.SourceControlHistoryProvi
9595
// A missing side makes VS Code open the existing file directly for additions/deletions.
9696
const originalUri = !parentId || file.status === FileChangeStatus.Added ? undefined : file.baseFileUri;
9797
const modifiedUri = file.status === FileChangeStatus.Removed ? undefined : file.headFileUri;
98-
// Carry context for our diff editor commands (open either side, previous/next revision).
99-
const query =
100-
originalUri && modifiedUri
101-
? queryString.stringify({
102-
base: originalUri.with({ query: '' }).toString(),
103-
head: modifiedUri.with({ query: '' }).toString(),
104-
status: file.status,
105-
})
106-
: '';
10798
return {
10899
// Display resource for the file label and status badge, including deleted files.
109100
uri: file.headFileUri.with({ query: queryString.stringify({ changeStatus: file.status }) }),
110101
// Content resources for the diff's left (before) and right (after) sides.
111-
originalUri: originalUri?.with({ query }),
112-
modifiedUri: modifiedUri?.with({ query }),
102+
originalUri,
103+
modifiedUri,
113104
};
114105
});
115106
}

‎extensions/github1s/src/commands/commit.ts‎

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -99,9 +99,11 @@ const commandDiffCommitFile = async (commitItem: CommitTreeItem) => {
9999
if (!activeDocumentUri) {
100100
return;
101101
}
102-
const fileUri = router.buildUri({ ref: commitSha }, activeDocumentUri).with({
103-
query: queryString.stringify({ from: router.getState().ref }),
104-
});
102+
const from = await Repository.getCurrentInstance().getCommitItem(router.getState().ref);
103+
if (!from) {
104+
return;
105+
}
106+
const fileUri = router.buildUri({ ref: commitSha }, activeDocumentUri);
105107
return vscode.commands.executeCommand('github1s.commands.openFilePreviousRevision', fileUri);
106108
};
107109

‎extensions/github1s/src/commands/editor.ts‎

Lines changed: 115 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { emptyFileUri } from '@/providers';
1010
import { FileChangeStatus } from '@/adapters/types';
1111
import { Repository } from '@/repository';
1212
import { getChangedFiles, getChangedFileDiffCommand, getChangedFileDiffTitle } from '@/changes/files';
13+
import { omit } from '@/helpers/util';
1314

1415
export const getChangedFileFromSourceControl = async (fileUri: vscode.Uri) => {
1516
// the file should belong to current workspace
@@ -35,114 +36,136 @@ const commandDiffChangedFile = async (fileUri: vscode.Uri) => {
3536
vscode.commands.executeCommand(command.command, ...(command.arguments || []));
3637
};
3738

38-
const openFileToEditor = async (fileUri) => {
39-
return vscode.commands.executeCommand('vscode.open', fileUri, { preview: false });
39+
const isRepositoryFileUri = (uri: vscode.Uri | undefined): uri is vscode.Uri => {
40+
return !!uri && /^(github1s|gitlab1s|bitbucket1s)$/.test(uri.scheme);
4041
};
4142

42-
// open the left file in the diff editor title
43-
const commandDiffViewOpenLeftFile = async (fileUri: vscode.Uri) => {
44-
const query = queryString.parse(fileUri?.query || '');
45-
return query.base ? openFileToEditor(vscode.Uri.parse(query.base as string)) : null;
46-
};
47-
48-
// open the right file in the diff editor title
49-
const commandDiffViewOpenRightFile = async (fileUri: vscode.Uri) => {
50-
const query = queryString.parse(fileUri?.query || '');
51-
return query.head ? openFileToEditor(vscode.Uri.parse(query.head as string)) : null;
52-
};
53-
54-
// get the file uri with the concrete commit sha, the `ref` in
55-
// `fileUri.authority` maybe newer but not related this file
56-
const getConcreteFileUri = async (fileUri: vscode.Uri) => {
57-
const { scheme, repo, ref, path } = router.parseUri(fileUri);
58-
const repository = Repository.getInstance(scheme, repo);
59-
const commit = await repository.getFileLatestCommit(ref, path);
60-
const latestCommitSha = commit?.sha || (await repository.getCommitItem(ref))?.sha;
43+
const getActiveDiffInput = (resource?: vscode.Uri): vscode.TabInputTextDiff | undefined => {
44+
const input = vscode.window.tabGroups.activeTabGroup.activeTab?.input;
45+
if (!(input instanceof vscode.TabInputTextDiff)) {
46+
return;
47+
}
6148

62-
return router.buildUri({ ref: latestCommitSha }, fileUri);
49+
// Title actions receive the modified URI. Ignore actions targeting another diff.
50+
if (resource && resource.toString() !== input.modified.toString()) {
51+
return;
52+
}
53+
return input;
6354
};
6455

65-
// show the file's diff between current commit and previous commit
66-
const commandOpenFilePreviousRevision = async (fileUri: vscode.Uri) => {
67-
const queryParams = queryString.parse(fileUri.query);
68-
const queryBaseUriStr = queryParams.base;
69-
const headFileUri = typeof queryParams.head === 'string' ? vscode.Uri.parse(queryParams.head) : undefined;
70-
const from =
71-
typeof queryParams.from === 'string'
72-
? queryParams.from
73-
: router.parseUri(headFileUri && headFileUri.scheme !== emptyFileUri.scheme ? headFileUri : fileUri).ref;
74-
const rightFileUri = await getConcreteFileUri(
75-
// if the `queryBaseUriStr` is empty, which means this command is called from
76-
// a normal file editor (not a diff editor), just use `fileUri` in this case
77-
queryBaseUriStr ? vscode.Uri.parse(queryBaseUriStr as string) : fileUri,
78-
);
79-
const { scheme, repo, ref: rightCommitSha } = router.parseUri(rightFileUri);
80-
const repository = Repository.getInstance(scheme, repo);
81-
const leftCommit = await repository.getPreviousCommit(rightCommitSha, rightFileUri.path, from);
82-
// if we can't find previous commit, use the `emptyFileUri` as the leftFileUri
83-
const leftFileUri = leftCommit ? router.buildUri({ ref: leftCommit.sha }, rightFileUri) : emptyFileUri;
84-
85-
const changedStatus = leftCommit ? FileChangeStatus.Modified : FileChangeStatus.Added;
86-
const hasNextRevision = !!(await repository.getNextCommit(rightCommitSha, rightFileUri.path, from));
87-
88-
const query = queryString.stringify({
89-
base: leftFileUri.with({ query: '' }).toString(),
90-
head: rightFileUri.with({ query: '' }).toString(),
91-
from,
92-
status: changedStatus,
93-
// if we can't find a newer commit for this file,
94-
// the `Show Next Commit` Button would be disabled.
95-
hasNextRevision,
96-
});
97-
98-
return vscode.commands.executeCommand(
99-
'vscode.diff',
100-
leftFileUri.with({ query }),
101-
rightFileUri.with({ query }),
102-
getChangedFileDiffTitle(leftFileUri, rightFileUri, changedStatus),
103-
);
56+
const createCommandDiffViewOpenFile = (side: 'original' | 'modified') => async (resource?: vscode.Uri) => {
57+
const fileUri = getActiveDiffInput(resource)?.[side];
58+
if (fileUri && fileUri?.scheme !== emptyFileUri.scheme) {
59+
await vscode.commands.executeCommand('workbench.action.keepEditor');
60+
return vscode.commands.executeCommand('vscode.open', fileUri, {});
61+
}
10462
};
10563

106-
// show the file's diff between current commit and next commit
107-
const commandOpenFileNextRevision = async (fileUri: vscode.Uri) => {
108-
const queryParams = queryString.parse(fileUri.query);
109-
const headFileUri = typeof queryParams.head === 'string' ? vscode.Uri.parse(queryParams.head) : fileUri;
110-
const from = typeof queryParams.from === 'string' ? queryParams.from : router.parseUri(headFileUri).ref;
111-
const leftFileUri = await getConcreteFileUri(headFileUri);
112-
113-
const { scheme, repo, ref: leftCommitSha } = router.parseUri(leftFileUri);
114-
const repository = Repository.getInstance(scheme, repo);
115-
const rightCommit = await repository.getNextCommit(leftCommitSha, leftFileUri.path, from);
64+
const resolveOpenFileRevisionArgs = async (
65+
fileUri: vscode.Uri | undefined,
66+
direction: 'previous' | 'next',
67+
): Promise<[vscode.Uri, string]> => {
68+
let baseUri: vscode.Uri | undefined, from: string | undefined;
69+
const getQueryFrom = (uri: vscode.Uri): string | undefined => {
70+
return queryString.parse(uri.query).from as string | undefined;
71+
};
72+
73+
const textDiffInput = getActiveDiffInput(fileUri);
74+
if (textDiffInput) {
75+
// this is a diff editor
76+
const { original, modified } = textDiffInput;
77+
const hasLeftFile = isRepositoryFileUri(original);
78+
const hasRightFile = isRepositoryFileUri(modified);
79+
80+
if (direction === 'previous' && hasLeftFile) {
81+
baseUri = original;
82+
}
83+
if (direction === 'next' && hasRightFile) {
84+
baseUri = modified;
85+
}
86+
if (hasRightFile) {
87+
from = getQueryFrom(modified);
88+
}
89+
} else if (isRepositoryFileUri(fileUri)) {
90+
// this is a single file editor
91+
from = getQueryFrom(fileUri);
92+
baseUri = fileUri;
93+
}
11694

117-
if (!rightCommit) {
118-
return vscode.window.showInformationMessage('There is no next commit found.');
95+
if (!baseUri) {
96+
throw new Error('Unable to resolve the target file.');
11997
}
12098

121-
const rightFileUri = router.buildUri({ ref: rightCommit.sha }, leftFileUri);
122-
const hasNextRevision = !!(await repository.getNextCommit(rightCommit.sha, rightFileUri.path, from));
99+
if (!from) {
100+
// If 'from' cannot be obtained in the query, use the ref of baseUri as 'from'
101+
const { scheme, repo, ref, path } = router.parseUri(baseUri);
102+
const repository = Repository.getInstance(scheme, repo);
103+
from = (await repository.getFileLatestCommit(ref, path))?.sha;
104+
if (!from) {
105+
throw new Error('Unable to resolve the latest commit for this file.');
106+
}
107+
baseUri = router.buildUri({ ref: from }, baseUri);
108+
}
123109

124-
const query = queryString.stringify({
125-
base: leftFileUri.with({ query: '' }).toString(),
126-
head: rightFileUri.with({ query: '' }).toString(),
127-
from,
128-
status: FileChangeStatus.Modified,
129-
hasNextRevision,
130-
});
110+
return [baseUri, from];
111+
};
131112

132-
return vscode.commands.executeCommand(
133-
'vscode.diff',
134-
leftFileUri.with({ query }),
135-
rightFileUri.with({ query }),
136-
getChangedFileDiffTitle(leftFileUri, rightFileUri, FileChangeStatus.Modified),
137-
);
113+
const createCommandOpenFileRevision = (direction: 'previous' | 'next') => async (fileUri?: vscode.Uri) => {
114+
try {
115+
const [baseUri, from] = await resolveOpenFileRevisionArgs(fileUri, direction);
116+
const { scheme, repo, ref, path } = router.parseUri(baseUri);
117+
const repository = Repository.getInstance(scheme, repo);
118+
const baseSha = (await repository.getCommitItem(ref))?.sha;
119+
if (!baseSha) {
120+
throw new Error('Unable to resolve the commit for this file.');
121+
}
122+
123+
let leftFileUri: vscode.Uri | undefined, rightFileUri: vscode.Uri | undefined;
124+
if (direction === 'previous') {
125+
const prevCommit = await repository.getPreviousCommit(baseSha, path, from);
126+
leftFileUri = prevCommit ? router.buildUri({ ref: prevCommit.sha }, baseUri) : emptyFileUri;
127+
rightFileUri = baseUri;
128+
} else {
129+
const nextCommit = await repository.getNextCommit(baseSha, path, from);
130+
if (!nextCommit) throw new Error('Unable to find next commit for this file.');
131+
leftFileUri = baseUri;
132+
rightFileUri = router.buildUri({ ref: nextCommit.sha }, baseUri);
133+
}
134+
135+
const hasNext = router.parseUri(rightFileUri).ref !== from || undefined;
136+
const leftQuery = queryString.stringify(omit(queryString.parse(baseUri.query), ['from']));
137+
const rightQuery = queryString.stringify({ ...queryString.parse(baseUri.query), from, hasNext });
138+
139+
if (fileUri && !queryString.parse(fileUri.query).from) {
140+
await vscode.commands.executeCommand('workbench.action.keepEditor');
141+
}
142+
143+
return await vscode.commands.executeCommand(
144+
'vscode.diff',
145+
leftFileUri.with({ query: leftQuery }),
146+
rightFileUri.with({ query: rightQuery }),
147+
getChangedFileDiffTitle(leftFileUri, rightFileUri, FileChangeStatus.Modified),
148+
);
149+
} catch (error) {
150+
return vscode.window.showErrorMessage(`Unable to open file revision: ${error.message}`);
151+
}
138152
};
139153

140154
export const registerEditorCommands = (context: vscode.ExtensionContext) => {
141155
return context.subscriptions.push(
142156
vscode.commands.registerCommand('github1s.commands.diffChangedFile', commandDiffChangedFile),
143-
vscode.commands.registerCommand('github1s.commands.diffViewOpenLeftFile', commandDiffViewOpenLeftFile),
144-
vscode.commands.registerCommand('github1s.commands.diffViewOpenRightFile', commandDiffViewOpenRightFile),
145-
vscode.commands.registerCommand('github1s.commands.openFilePreviousRevision', commandOpenFilePreviousRevision),
146-
vscode.commands.registerCommand('github1s.commands.openFileNextRevision', commandOpenFileNextRevision),
157+
vscode.commands.registerCommand(
158+
'github1s.commands.diffViewOpenLeftFile',
159+
createCommandDiffViewOpenFile('original'),
160+
),
161+
vscode.commands.registerCommand(
162+
'github1s.commands.diffViewOpenRightFile',
163+
createCommandDiffViewOpenFile('modified'),
164+
),
165+
vscode.commands.registerCommand(
166+
'github1s.commands.openFilePreviousRevision',
167+
createCommandOpenFileRevision('previous'),
168+
),
169+
vscode.commands.registerCommand('github1s.commands.openFileNextRevision', createCommandOpenFileRevision('next')),
147170
);
148171
};

0 commit comments

Comments
 (0)