|
1 |
| -// import path from "path"; |
2 |
| -import type { Disposable, Uri } from "vscode"; |
3 |
| -import { window } from "vscode"; |
4 |
| -import { Container } from "../container"; |
5 |
| -import type { GitUri } from "../git/gitUri"; |
6 |
| -import { filterMap } from "../system/iterable"; |
7 |
| -import { getQuickPickIgnoreFocusOut } from "../system/utils"; |
| 1 | +import type { Disposable, Uri } from 'vscode'; |
| 2 | +import { window } from 'vscode'; |
| 3 | +import type { Keys } from '../constants'; |
| 4 | +import type { Container } from '../container'; |
| 5 | +import type { GitUri } from '../git/gitUri'; |
| 6 | +import type { GitTreeEntry } from '../git/models/tree'; |
| 7 | +import { filterMap } from '../system/iterable'; |
| 8 | +import type { KeyboardScope } from '../system/keyboard'; |
| 9 | +import { splitPath } from '../system/path'; |
| 10 | +import { getQuickPickIgnoreFocusOut } from '../system/utils'; |
| 11 | +import type { QuickPickItemOfT } from './items/common'; |
| 12 | + |
| 13 | +export type RevisionQuickPickItem = QuickPickItemOfT<GitTreeEntry>; |
8 | 14 |
|
9 | 15 | export async function showRevisionPicker(
|
| 16 | + container: Container, |
10 | 17 | uri: GitUri,
|
11 | 18 | options: {
|
12 |
| - title: string; |
| 19 | + ignoreFocusOut?: boolean; |
13 | 20 | initialPath?: string;
|
| 21 | + keyboard?: { |
| 22 | + keys: Keys[]; |
| 23 | + onDidPressKey(key: Keys, uri: Uri): void | Promise<void>; |
| 24 | + }; |
| 25 | + placeholder?: string; |
| 26 | + title: string; |
14 | 27 | },
|
15 | 28 | ): Promise<Uri | undefined> {
|
16 | 29 | const disposables: Disposable[] = [];
|
| 30 | + |
| 31 | + const repoPath = uri.repoPath!; |
| 32 | + const ref = uri.sha!; |
| 33 | + |
| 34 | + function getRevisionUri(item: RevisionQuickPickItem) { |
| 35 | + return container.git.getRevisionUri(ref, `${repoPath}/${item.item.path}`, repoPath); |
| 36 | + } |
| 37 | + |
17 | 38 | try {
|
18 |
| - const picker = window.createQuickPick(); |
19 |
| - picker.title = options.title; |
20 |
| - picker.value = options.initialPath ?? uri.relativePath; |
21 |
| - picker.placeholder = 'Enter path to file...'; |
22 |
| - picker.matchOnDescription = true; |
23 |
| - picker.busy = true; |
24 |
| - picker.ignoreFocusOut = getQuickPickIgnoreFocusOut(); |
25 |
| - |
26 |
| - picker.show(); |
27 |
| - |
28 |
| - const tree = await Container.instance.git.getTreeForRevision(uri.repoPath, uri.sha!); |
29 |
| - picker.items = Array.from(filterMap(tree, file => { |
30 |
| - // Exclude directories |
31 |
| - if (file.type !== 'blob') { return null } |
32 |
| - return { label: file.path } |
33 |
| - // FIXME: Remove this unless we opt to show the directory in the description |
34 |
| - // const parsed = path.parse(file.path) |
35 |
| - // return { label: parsed.base, description: parsed.dir } |
36 |
| - })) |
37 |
| - picker.busy = false; |
38 |
| - |
39 |
| - const pick = await new Promise<string | undefined>(resolve => { |
| 39 | + const quickpick = window.createQuickPick<RevisionQuickPickItem>(); |
| 40 | + quickpick.ignoreFocusOut = options?.ignoreFocusOut ?? getQuickPickIgnoreFocusOut(); |
| 41 | + |
| 42 | + const value = options.initialPath ?? uri.relativePath; |
| 43 | + |
| 44 | + let scope: KeyboardScope | undefined; |
| 45 | + if (options?.keyboard != null) { |
| 46 | + const { keyboard } = options; |
| 47 | + scope = container.keyboard.createScope( |
| 48 | + Object.fromEntries( |
| 49 | + keyboard.keys.map(key => [ |
| 50 | + key, |
| 51 | + { |
| 52 | + onDidPressKey: async key => { |
| 53 | + if (quickpick.activeItems.length !== 0) { |
| 54 | + const [item] = quickpick.activeItems; |
| 55 | + if (item.item != null) { |
| 56 | + const ignoreFocusOut = quickpick.ignoreFocusOut; |
| 57 | + quickpick.ignoreFocusOut = true; |
| 58 | + |
| 59 | + await keyboard.onDidPressKey(key, getRevisionUri(item)); |
| 60 | + |
| 61 | + quickpick.ignoreFocusOut = ignoreFocusOut; |
| 62 | + } |
| 63 | + } |
| 64 | + }, |
| 65 | + }, |
| 66 | + ]), |
| 67 | + ), |
| 68 | + ); |
| 69 | + void scope.start(); |
| 70 | + if (value != null) { |
| 71 | + void scope.pause(['left', 'ctrl+left', 'right', 'ctrl+right']); |
| 72 | + } |
| 73 | + disposables.push(scope); |
| 74 | + } |
| 75 | + |
| 76 | + quickpick.title = options.title; |
| 77 | + quickpick.placeholder = options?.placeholder ?? 'Search files by name'; |
| 78 | + quickpick.matchOnDescription = true; |
| 79 | + |
| 80 | + quickpick.value = value; |
| 81 | + quickpick.busy = true; |
| 82 | + quickpick.show(); |
| 83 | + |
| 84 | + const tree = await container.git.getTreeForRevision(uri.repoPath, ref); |
| 85 | + const items: RevisionQuickPickItem[] = [ |
| 86 | + ...filterMap(tree, file => { |
| 87 | + // Exclude directories |
| 88 | + if (file.type !== 'blob') return undefined; |
| 89 | + |
| 90 | + const [label, description] = splitPath(file.path, undefined, true); |
| 91 | + return { |
| 92 | + label: label, |
| 93 | + description: description === '.' ? '' : description, |
| 94 | + item: file, |
| 95 | + } satisfies RevisionQuickPickItem; |
| 96 | + }), |
| 97 | + ]; |
| 98 | + quickpick.items = items; |
| 99 | + quickpick.busy = false; |
| 100 | + |
| 101 | + const pick = await new Promise<RevisionQuickPickItem | undefined>(resolve => { |
40 | 102 | disposables.push(
|
41 |
| - picker, |
42 |
| - picker.onDidHide(() => resolve(undefined)), |
43 |
| - picker.onDidAccept(() => { |
44 |
| - if (picker.activeItems.length === 0) return; |
45 |
| - resolve(picker.activeItems[0].label); |
| 103 | + quickpick, |
| 104 | + quickpick.onDidHide(() => resolve(undefined)), |
| 105 | + quickpick.onDidAccept(() => { |
| 106 | + if (quickpick.activeItems.length === 0) return; |
| 107 | + |
| 108 | + resolve(quickpick.activeItems[0]); |
| 109 | + }), |
| 110 | + quickpick.onDidChangeValue(value => { |
| 111 | + if (scope == null) return; |
| 112 | + |
| 113 | + // Pause the left/right keyboard commands if there is a value, otherwise the left/right arrows won't work in the input properly |
| 114 | + if (value.length !== 0) { |
| 115 | + void scope.pause(['left', 'ctrl+left', 'right', 'ctrl+right']); |
| 116 | + } else { |
| 117 | + void scope.resume(); |
| 118 | + } |
| 119 | + |
| 120 | + for (const item of items) { |
| 121 | + if ( |
| 122 | + item.item.path.includes(value) && |
| 123 | + !item.label.includes(value) && |
| 124 | + !item.description!.includes(value) |
| 125 | + ) { |
| 126 | + item.alwaysShow = true; |
| 127 | + } else { |
| 128 | + item.alwaysShow = false; |
| 129 | + } |
| 130 | + } |
| 131 | + quickpick.items = items; |
46 | 132 | }),
|
47 | 133 | );
|
48 | 134 | });
|
49 | 135 |
|
50 |
| - return pick |
51 |
| - ? Container.instance.git.getRevisionUri(uri.sha!, `${uri.repoPath}/${pick}`, uri.repoPath!) |
52 |
| - : undefined; |
| 136 | + return pick != null ? getRevisionUri(pick) : undefined; |
53 | 137 | } finally {
|
54 |
| - disposables.forEach(d => { |
55 |
| - d.dispose(); |
56 |
| - }); |
| 138 | + disposables.forEach(d => void d.dispose()); |
57 | 139 | }
|
58 | 140 | }
|
0 commit comments