Skip to content

Commit

Permalink
Fixes gitkraken#708 - Prompt for file path when missing from revision
Browse files Browse the repository at this point in the history
Improves the `openFileAtRevision()` function by:
- Asking user to enter another path when the requested file doesn't exist in the
  selected revision.
- Showing an error message on subsequent failure (or if an error is thrown).

The above function is used by a number of commands:
- `gitlens.openFileRevision`
- `gitlens.openFileRevisionFrom`
- `gitlens.openRevisionFile`

Also renames the `rethrow` argument of `utils.openEditor()` to `throwOnError` to
align it with `utils.findOrOpenEditor()`.
  • Loading branch information
mogelbrod committed Nov 16, 2023
1 parent 81c0a12 commit 4e7c81e
Show file tree
Hide file tree
Showing 4 changed files with 77 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Added

- Adds support for OpenAI's GPT-4 Turbo and latest Anthropic models for GitLens' experimental AI features — closes [#3005](https://github.com/gitkraken/vscode-gitlens/issues/3005)
- Adds support for opening renamed/deleted files using the _Open File at Revision..._ commands by showing a quickpick prompt if the requested file doesn't exist in the selected revision — thanks to [PR #2825](https://github.com/gitkraken/vscode-gitlens/pull/2825) by Victor Hallberg ([@mogelbrod](https://github.com/mogelbrod))

### Changed

Expand Down
2 changes: 1 addition & 1 deletion src/commands/openFileFromRemote.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ export class OpenFileFromRemoteCommand extends Command {
}

try {
await openEditor(local.uri, { selection: selection, rethrow: true });
await openEditor(local.uri, { selection: selection, throwOnError: true });
} catch {
const uris = await window.showOpenDialog({
title: 'Open local file',
Expand Down
80 changes: 72 additions & 8 deletions src/git/actions/commit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { TextDocumentShowOptions } from 'vscode';
import type { QuickPickItem, TextDocumentShowOptions, TextEditor } from 'vscode';
import { env, Range, Uri, window, workspace } from 'vscode';
import type { Disposable } from '../../api/gitlens';
import type { DiffWithCommandArgs } from '../../commands/diffWith';
import type { DiffWithPreviousCommandArgs } from '../../commands/diffWithPrevious';
import type { DiffWithWorkingCommandArgs } from '../../commands/diffWithWorking';
Expand All @@ -13,7 +14,7 @@ import { Commands } from '../../constants';
import { Container } from '../../container';
import type { ShowInCommitGraphCommandArgs } from '../../plus/webviews/graph/protocol';
import { executeCommand, executeEditorCommand } from '../../system/command';
import { findOrOpenEditor, findOrOpenEditors } from '../../system/utils';
import { findOrOpenEditor, findOrOpenEditors, getQuickPickIgnoreFocusOut } from '../../system/utils';
import { GitUri } from '../gitUri';
import type { GitCommit } from '../models/commit';
import { isCommit } from '../models/commit';
Expand Down Expand Up @@ -402,7 +403,7 @@ export async function openFileAtRevision(
commitOrOptions?: GitCommit | TextDocumentShowOptions,
options?: TextDocumentShowOptions & { annotationType?: FileAnnotationType; line?: number },
): Promise<void> {
let uri;
let uri: Uri;
if (fileOrRevisionUri instanceof Uri) {
if (isCommit(commitOrOptions)) throw new Error('Invalid arguments');

Expand Down Expand Up @@ -440,11 +441,74 @@ export async function openFileAtRevision(
opts.selection = new Range(line, 0, line, 0);
}

const editor = await findOrOpenEditor(uri, opts);
if (annotationType != null && editor != null) {
void (await Container.instance.fileAnnotations.show(editor, annotationType, {
selection: { line: line },
}));
const gitUri = await GitUri.fromUri(uri);

let editor: TextEditor | undefined;
try {
editor = await findOrOpenEditor(uri, { throwOnError: true, ...opts }).catch(error => {
if (error?.message?.includes('Unable to resolve nonexistent file')) {
return pickFileAtRevision(gitUri, {
title: 'File not found in revision - pick another file to open instead',
}).then(pickedUri => {
return pickedUri ? findOrOpenEditor(pickedUri, opts) : undefined;
});
}
throw error;
});

if (annotationType != null && editor != null) {
void (await Container.instance.fileAnnotations.show(editor, annotationType, {
selection: { line: line },
}));
}
} catch (error) {
await window.showErrorMessage(
`Unable to open '${gitUri.relativePath}' - file doesn't exist in selected revision`,
);
}
}

export async function pickFileAtRevision(
uri: GitUri,
options: {
title: string;
initialPath?: string;
},
): Promise<Uri | undefined> {
const disposables: Disposable[] = [];
try {
const picker = window.createQuickPick();
Object.assign(picker, {
title: options.title,
value: options.initialPath ?? uri.relativePath,
placeholder: 'Enter path to file...',
busy: true,
ignoreFocusOut: getQuickPickIgnoreFocusOut(),
});
picker.show();

const tree = await Container.instance.git.getTreeForRevision(uri.repoPath, uri.sha!);
picker.items = tree.filter(file => file.type === 'blob').map((file): QuickPickItem => ({ label: file.path }));
picker.busy = false;

const pick = await new Promise<string | undefined>(resolve => {
disposables.push(
picker,
picker.onDidHide(() => resolve(undefined)),
picker.onDidAccept(() => {
if (picker.activeItems.length === 0) return;
resolve(picker.activeItems[0].label);
}),
);
});

return pick
? Container.instance.git.getRevisionUri(uri.sha!, `${uri.repoPath}/${pick}`, uri.repoPath!)
: undefined;
} finally {
disposables.forEach(d => {
d.dispose();
});
}
}

Expand Down
6 changes: 3 additions & 3 deletions src/system/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,9 @@ export function isTextEditor(editor: TextEditor): boolean {

export async function openEditor(
uri: Uri,
options: TextDocumentShowOptions & { rethrow?: boolean } = {},
options: TextDocumentShowOptions & { throwOnError?: boolean } = {},
): Promise<TextEditor | undefined> {
const { rethrow, ...opts } = options;
const { throwOnError, ...opts } = options;
try {
if (isGitUri(uri)) {
uri = uri.documentUri();
Expand All @@ -154,7 +154,7 @@ export async function openEditor(
return undefined;
}

if (rethrow) throw ex;
if (throwOnError) throw ex;

Logger.error(ex, 'openEditor');
return undefined;
Expand Down

0 comments on commit 4e7c81e

Please sign in to comment.