From 4e7c81e9121a29eb4c89f84ca70eed64ca9916aa Mon Sep 17 00:00:00 2001 From: Victor Hallberg Date: Thu, 20 Jul 2023 19:49:40 +0200 Subject: [PATCH] Fixes #708 - Prompt for file path when missing from revision 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()`. --- CHANGELOG.md | 1 + src/commands/openFileFromRemote.ts | 2 +- src/git/actions/commit.ts | 80 +++++++++++++++++++++++++++--- src/system/utils.ts | 6 +-- 4 files changed, 77 insertions(+), 12 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 993438dc7b920..dcd0537dd3739 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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 diff --git a/src/commands/openFileFromRemote.ts b/src/commands/openFileFromRemote.ts index b5d62386442c6..eb17a9cde5365 100644 --- a/src/commands/openFileFromRemote.ts +++ b/src/commands/openFileFromRemote.ts @@ -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', diff --git a/src/git/actions/commit.ts b/src/git/actions/commit.ts index 600f9be778b1f..aca0f447ca8ee 100644 --- a/src/git/actions/commit.ts +++ b/src/git/actions/commit.ts @@ -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'; @@ -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'; @@ -402,7 +403,7 @@ export async function openFileAtRevision( commitOrOptions?: GitCommit | TextDocumentShowOptions, options?: TextDocumentShowOptions & { annotationType?: FileAnnotationType; line?: number }, ): Promise { - let uri; + let uri: Uri; if (fileOrRevisionUri instanceof Uri) { if (isCommit(commitOrOptions)) throw new Error('Invalid arguments'); @@ -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 { + 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(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(); + }); } } diff --git a/src/system/utils.ts b/src/system/utils.ts index 19ec57a1a2ba3..c633b10d695d3 100644 --- a/src/system/utils.ts +++ b/src/system/utils.ts @@ -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 { - const { rethrow, ...opts } = options; + const { throwOnError, ...opts } = options; try { if (isGitUri(uri)) { uri = uri.documentUri(); @@ -154,7 +154,7 @@ export async function openEditor( return undefined; } - if (rethrow) throw ex; + if (throwOnError) throw ex; Logger.error(ex, 'openEditor'); return undefined;