@@ -10,6 +10,7 @@ import { emptyFileUri } from '@/providers';
1010import { FileChangeStatus } from '@/adapters/types' ;
1111import { Repository } from '@/repository' ;
1212import { getChangedFiles , getChangedFileDiffCommand , getChangedFileDiffTitle } from '@/changes/files' ;
13+ import { omit } from '@/helpers/util' ;
1314
1415export 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 && / ^ ( g i t h u b 1 s | g i t l a b 1 s | b i t b u c k e t 1 s ) $ / . 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
140154export 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