text:
import { QuickInputButton, QuickPick } from 'vscode';
import { BranchSorting, configuration, TagSorting } from '../configuration';
import { Commands, GlyphChars, quickPickTitleMaxChars } from '../constants';
import { Container } from '../container';
import type { PlusFeatures } from '../features';
import type { PagedResult } from '../git/gitProvider';
import {
	BranchSortOptions,
	GitBranch,
	GitBranchReference,
	GitCommit,
	GitContributor,
	GitLog,
	GitReference,
	GitRemote,
	GitRevision,
	GitRevisionReference,
	GitStash,
	GitStashCommit,
	GitStatus,
	GitTag,
	GitTagReference,
	GitWorktree,
	Repository,
	TagSortOptions,
} from '../git/models';
import { RemoteResourceType } from '../git/remotes/provider';
import { SearchPattern } from '../git/search';
import {
	CommitApplyFileChangesCommandQuickPickItem,
	CommitBrowseRepositoryFromHereCommandQuickPickItem,
	CommitCompareWithHEADCommandQuickPickItem,
	CommitCompareWithWorkingCommandQuickPickItem,
	CommitCopyIdQuickPickItem,
	CommitCopyMessageQuickPickItem,
	CommitFileQuickPickItem,
	CommitFilesQuickPickItem,
	CommitOpenAllChangesCommandQuickPickItem,
	CommitOpenAllChangesWithDiffToolCommandQuickPickItem,
	CommitOpenAllChangesWithWorkingCommandQuickPickItem,
	CommitOpenChangesCommandQuickPickItem,
	CommitOpenChangesWithDiffToolCommandQuickPickItem,
	CommitOpenChangesWithWorkingCommandQuickPickItem,
	CommitOpenDirectoryCompareCommandQuickPickItem,
	CommitOpenDirectoryCompareWithWorkingCommandQuickPickItem,
	CommitOpenFileCommandQuickPickItem,
	CommitOpenFilesCommandQuickPickItem,
	CommitOpenRevisionCommandQuickPickItem,
	CommitOpenRevisionsCommandQuickPickItem,
	CommitRestoreFileChangesCommandQuickPickItem,
	OpenChangedFilesCommandQuickPickItem,
} from '../quickpicks/items/commits';
import { CommandQuickPickItem, QuickPickSeparator } from '../quickpicks/items/common';
import { Directive, DirectiveQuickPickItem } from '../quickpicks/items/directive';
import {
	BranchQuickPickItem,
	CommitQuickPickItem,
	ContributorQuickPickItem,
	GitCommandQuickPickItem,
	RefQuickPickItem,
	RepositoryQuickPickItem,
	TagQuickPickItem,
	WorktreeQuickPickItem,
} from '../quickpicks/items/gitCommands';
import { ReferencesQuickPickItem } from '../quickpicks/referencePicker';
import {
	CopyRemoteResourceCommandQuickPickItem,
	OpenRemoteResourceCommandQuickPickItem,
} from '../quickpicks/remoteProviderPicker';
import { isSubscriptionPaidPlan, isSubscriptionPreviewTrialExpired } from '../subscription';
import { filterMap, intersection, isStringArray } from '../system/array';
import { formatPath } from '../system/formatPath';
import { map } from '../system/iterable';
import { pad, pluralize, truncate } from '../system/string';
import { OpenWorkspaceLocation } from '../system/utils';
import type { ViewsWithRepositoryFolders } from '../views/viewBase';
import { GitActions } from './gitCommands.actions';
import {
	AsyncStepResultGenerator,
	PartialStepState,
	QuickCommand,
	QuickCommandButtons,
	QuickPickStep,
	StepResult,
	StepResultGenerator,
	StepSelection,
	StepState,
} from './quickCommand';

export function appendReposToTitle<
	State extends { repo: Repository } | { repos: Repository[] },
	Context extends { repos: Repository[] },
>(title: string, state: State, context: Context, additionalContext?: string) {
	if (context.repos.length === 1) {
		return `${title}${truncate(additionalContext ?? '', quickPickTitleMaxChars - title.length)}`;
	}

	let repoContext;
	if ((state as { repo: Repository }).repo != null) {
		repoContext = `${additionalContext ?? ''}${pad(GlyphChars.Dot, 2, 2)}${
			(state as { repo: Repository }).repo.formattedName
		}`;
	} else if ((state as { repos: Repository[] }).repos.length === 1) {
		repoContext = `${additionalContext ?? ''}${pad(GlyphChars.Dot, 2, 2)}${
			(state as { repos: Repository[] }).repos[0].formattedName
		}`;
	} else {
		repoContext = `${pad(GlyphChars.Dot, 2, 2)}${(state as { repos: Repository[] }).repos.length} repositories`;
	}

	return `${title}${truncate(repoContext, quickPickTitleMaxChars - title.length)}`;
}

export async function getBranches(
	repos: Repository | Repository[],
	options: {
		buttons?: QuickInputButton[];
		filter?: (b: GitBranch) => boolean;
		picked?: string | string[];
		sort?: BranchSortOptions;
	},
): Promise<BranchQuickPickItem[]> {
	return getBranchesAndOrTags(repos, ['branches'], {
		buttons: options?.buttons,
		filter: options?.filter != null ? { branches: options.filter } : undefined,
		picked: options?.picked,
		sort: options?.sort != null ? { branches: options.sort } : true,
	}) as Promise<BranchQuickPickItem[]>;
}

export async function getTags(
	repos: Repository | Repository[],
	options?: {
		buttons?: QuickInputButton[];
		filter?: (t: GitTag) => boolean;
		picked?: string | string[];
		sort?: TagSortOptions;
	},
): Promise<TagQuickPickItem[]> {
	return getBranchesAndOrTags(repos, ['tags'], {
		buttons: options?.buttons,
		filter: options?.filter != null ? { tags: options.filter } : undefined,
		picked: options?.picked,
		sort: options?.sort != null ? { tags: options.sort } : true,
	}) as Promise<TagQuickPickItem[]>;
}

export async function getWorktrees(
	repoOrWorktrees: Repository | GitWorktree[],
	{
		buttons,
		filter,
		includeStatus,
		picked,
	}: {
		buttons?: QuickInputButton[];
		filter?: (t: GitWorktree) => boolean;
		includeStatus?: boolean;
		picked?: string | string[];
	},
): Promise<WorktreeQuickPickItem[]> {
	const worktrees = repoOrWorktrees instanceof Repository ? await repoOrWorktrees.getWorktrees() : repoOrWorktrees;
	return Promise.all<WorktreeQuickPickItem>([
		...worktrees
			.filter(w => filter == null || filter(w))
			.map(async w =>
				WorktreeQuickPickItem.create(
					w,
					picked != null &&
						(typeof picked === 'string' ? w.uri.toString() === picked : picked.includes(w.uri.toString())),
					{
						buttons: buttons,
						path: true,
						status: includeStatus ? await w.getStatus() : undefined,
					},
				),
			),
	]);
}

export async function getBranchesAndOrTags(
	repos: Repository | Repository[] | undefined,
	include: ('tags' | 'branches')[],
	{
		buttons,
		filter,
		picked,
		sort,
	}: {
		buttons?: QuickInputButton[];
		filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
		picked?: string | string[];
		sort?: boolean | { branches?: BranchSortOptions; tags?: TagSortOptions };
	} = {},
): Promise<(BranchQuickPickItem | TagQuickPickItem)[]> {
	if (repos == null) return [];

	let branches: GitBranch[] | undefined;
	let tags: GitTag[] | undefined;

	let singleRepo = false;
	if (repos instanceof Repository || repos.length === 1) {
		singleRepo = true;
		const repo = repos instanceof Repository ? repos : repos[0];

		// TODO@eamodio handle paging
		const [branchesResult, tagsResult] = await Promise.allSettled([
			include.includes('branches')
				? repo.getBranches({
						filter: filter?.branches,
						sort: typeof sort === 'boolean' ? sort : sort?.branches,
				  })
				: undefined,
			include.includes('tags') ? repo.getTags({ filter: filter?.tags, sort: true }) : undefined,
		]);

		branches = (branchesResult.status === 'fulfilled' ? branchesResult.value?.values : undefined) ?? [];
		tags = (tagsResult.status === 'fulfilled' ? tagsResult.value?.values : undefined) ?? [];
	} else {
		// TODO@eamodio handle paging
		const [branchesByRepoResult, tagsByRepoResult] = await Promise.allSettled([
			include.includes('branches')
				? Promise.allSettled(
						repos.map(r =>
							r.getBranches({
								filter: filter?.branches,
								sort: typeof sort === 'boolean' ? sort : sort?.branches,
							}),
						),
				  )
				: undefined,
			include.includes('tags')
				? Promise.allSettled(
						repos.map(r =>
							r.getTags({ filter: filter?.tags, sort: typeof sort === 'boolean' ? sort : sort?.tags }),
						),
				  )
				: undefined,
		]);

		const branchesByRepo =
			branchesByRepoResult.status === 'fulfilled'
				? branchesByRepoResult.value
						?.filter((r): r is PromiseFulfilledResult<PagedResult<GitBranch>> => r.status === 'fulfilled')
						?.map(r => r.value.values)
				: undefined;
		const tagsByRepo =
			tagsByRepoResult.status === 'fulfilled'
				? tagsByRepoResult.value
						?.filter((r): r is PromiseFulfilledResult<PagedResult<GitTag>> => r.status === 'fulfilled')
						?.map(r => r.value.values)
				: undefined;

		if (include.includes('branches') && branchesByRepo != null) {
			branches = GitBranch.sort(
				intersection(...branchesByRepo, (b1: GitBranch, b2: GitBranch) => b1.name === b2.name),
			);
		}

		if (include.includes('tags') && tagsByRepo != null) {
			tags = GitTag.sort(intersection(...tagsByRepo, (t1: GitTag, t2: GitTag) => t1.name === t2.name));
		}
	}

	if ((branches == null || branches.length === 0) && (tags == null || tags.length === 0)) return [];

	if (branches != null && branches.length !== 0 && (tags == null || tags.length === 0)) {
		return [
			QuickPickSeparator.create('Branches'),
			...(await Promise.all(
				branches
					.filter(b => !b.remote)
					.map(b =>
						BranchQuickPickItem.create(
							b,
							picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)),
							{
								buttons: buttons,
								current: singleRepo ? 'checkmark' : false,
								ref: singleRepo,
								status: singleRepo,
								type: 'remote',
							},
						),
					),
			)),
			QuickPickSeparator.create('Remote Branches'),
			...(await Promise.all(
				branches
					.filter(b => b.remote)
					.map(b =>
						BranchQuickPickItem.create(
							b,
							picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)),
							{
								buttons: buttons,
								current: singleRepo ? 'checkmark' : false,
								ref: singleRepo,
								status: singleRepo,
								type: 'remote',
							},
						),
					),
			)),
		] as BranchQuickPickItem[];
	}

	if (tags != null && tags.length !== 0 && (branches == null || branches.length === 0)) {
		return tags.map(t =>
			TagQuickPickItem.create(
				t,
				picked != null && (typeof picked === 'string' ? t.ref === picked : picked.includes(t.ref)),
				{
					buttons: buttons,
					message: false, //singleRepo,
					ref: singleRepo,
				},
			),
		);
	}

	return [
		QuickPickSeparator.create('Branches'),
		...(await Promise.all(
			branches!
				.filter(b => !b.remote)
				.map(b =>
					BranchQuickPickItem.create(
						b,
						picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)),
						{
							buttons: buttons,
							current: singleRepo ? 'checkmark' : false,
							ref: singleRepo,
							status: singleRepo,
						},
					),
				),
		)),
		QuickPickSeparator.create('Tags'),
		...tags!.map(t =>
			TagQuickPickItem.create(
				t,
				picked != null && (typeof picked === 'string' ? t.ref === picked : picked.includes(t.ref)),
				{
					buttons: buttons,
					message: false, //singleRepo,
					ref: singleRepo,
					type: true,
				},
			),
		),
		QuickPickSeparator.create('Remote Branches'),
		...(await Promise.all(
			branches!
				.filter(b => b.remote)
				.map(b =>
					BranchQuickPickItem.create(
						b,
						picked != null && (typeof picked === 'string' ? b.ref === picked : picked.includes(b.ref)),
						{
							buttons: buttons,
							current: singleRepo ? 'checkmark' : false,
							ref: singleRepo,
							status: singleRepo,
							type: 'remote',
						},
					),
				),
		)),
	] as (BranchQuickPickItem | TagQuickPickItem)[];
}

export function getValidateGitReferenceFn(
	repos: Repository | Repository[] | undefined,
	options?: { buttons?: QuickInputButton[]; ranges?: boolean },
) {
	return async (quickpick: QuickPick<any>, value: string) => {
		let inRefMode = false;
		if (value.startsWith('#')) {
			inRefMode = true;
			value = value.substring(1);
		}

		if (repos == null) return false;
		if (Array.isArray(repos)) {
			if (repos.length !== 1) return false;

			repos = repos[0];
		}

		if (inRefMode && options?.ranges && GitRevision.isRange(value)) {
			quickpick.items = [
				RefQuickPickItem.create(value, repos.path, true, {
					alwaysShow: true,
					buttons: options?.buttons,
					ref: false,
					icon: false,
				}),
			];
			return true;
		}

		if (!(await Container.instance.git.validateReference(repos.path, value))) {
			if (inRefMode) {
				quickpick.items = [
					DirectiveQuickPickItem.create(Directive.Back, true, {
						label: 'Enter a reference or commit SHA',
					}),
				];
				return true;
			}

			return false;
		}

		if (!inRefMode) {
			if (
				await Container.instance.git.hasBranchOrTag(repos.path, {
					filter: { branches: b => b.name.includes(value), tags: t => t.name.includes(value) },
				})
			) {
				return false;
			}
		}

		const commit = await Container.instance.git.getCommit(repos.path, value);
		quickpick.items = [
			CommitQuickPickItem.create(commit!, true, {
				alwaysShow: true,
				buttons: options?.buttons,
				compact: true,
				icon: true,
			}),
		];
		return true;
	};
}

export async function* inputBranchNameStep<
	State extends PartialStepState & ({ repo: Repository } | { repos: Repository[] }),
	Context extends { repos: Repository[]; showTags?: boolean; title: string },
>(
	state: State,
	context: Context,
	options: { placeholder: string; titleContext?: string; value?: string },
): AsyncStepResultGenerator<string> {
	const step = QuickCommand.createInputStep({
		title: appendReposToTitle(`${context.title}${options.titleContext ?? ''}`, state, context),
		placeholder: options.placeholder,
		value: options.value,
		prompt: 'Enter branch name',
		validate: async (value: string | undefined): Promise<[boolean, string | undefined]> => {
			if (value == null) return [false, undefined];

			value = value.trim();
			if (value.length === 0) return [false, 'Please enter a valid branch name'];

			if ('repo' in state) {
				const valid = await Container.instance.git.validateBranchOrTagName(state.repo.path, value);
				return [valid, valid ? undefined : `'${value}' isn't a valid branch name`];
			}

			let valid = true;

			for (const repo of state.repos) {
				valid = await Container.instance.git.validateBranchOrTagName(repo.path, value);
				if (!valid) {
					return [false, `'${value}' isn't a valid branch name`];
				}
			}

			return [true, undefined];
		},
	});

	const value: StepSelection<typeof step> = yield step;
	if (
		!QuickCommand.canStepContinue(step, state, value) ||
		!(await QuickCommand.canInputStepContinue(step, state, value))
	) {
		return StepResult.Break;
	}

	return value;
}

export async function* inputTagNameStep<
	State extends PartialStepState & ({ repo: Repository } | { repos: Repository[] }),
	Context extends { repos: Repository[]; showTags?: boolean; title: string },
>(
	state: State,
	context: Context,
	options: { placeholder: string; titleContext?: string; value?: string },
): AsyncStepResultGenerator<string> {
	const step = QuickCommand.createInputStep({
		title: appendReposToTitle(`${context.title}${options.titleContext ?? ''}`, state, context),
		placeholder: options.placeholder,
		value: options.value,
		prompt: 'Enter tag name',
		validate: async (value: string | undefined): Promise<[boolean, string | undefined]> => {
			if (value == null) return [false, undefined];

			value = value.trim();
			if (value.length === 0) return [false, 'Please enter a valid tag name'];

			if ('repo' in state) {
				const valid = await Container.instance.git.validateBranchOrTagName(state.repo.path, value);
				return [valid, valid ? undefined : `'${value}' isn't a valid tag name`];
			}

			let valid = true;

			for (const repo of state.repos) {
				valid = await Container.instance.git.validateBranchOrTagName(repo.path, value);
				if (!valid) {
					return [false, `'${value}' isn't a valid branch name`];
				}
			}

			return [true, undefined];
		},
	});

	const value: StepSelection<typeof step> = yield step;
	if (
		!QuickCommand.canStepContinue(step, state, value) ||
		!(await QuickCommand.canInputStepContinue(step, state, value))
	) {
		return StepResult.Break;
	}

	return value;
}

export async function* pickBranchStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; showTags?: boolean; title: string },
>(
	state: State,
	context: Context,
	{
		filter,
		picked,
		placeholder,
		titleContext,
	}: {
		filter?: (b: GitBranch) => boolean;
		picked?: string | string[];
		placeholder: string;
		titleContext?: string;
	},
): AsyncStepResultGenerator<GitBranchReference> {
	const branches = await getBranches(state.repo, {
		buttons: [QuickCommandButtons.RevealInSideBar],
		filter: filter,
		picked: picked,
	});

	const step = QuickCommand.createPickStep<BranchQuickPickItem>({
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder: branches.length === 0 ? `No branches found in ${state.repo.formattedName}` : placeholder,
		matchOnDetail: true,
		items:
			branches.length === 0
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: branches,
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				void GitActions.Branch.reveal(item, { select: true, focus: false, expand: true });
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async quickpick => {
			if (quickpick.activeItems.length === 0) return;

			await GitActions.Branch.reveal(quickpick.activeItems[0].item, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0].item : StepResult.Break;
}

export async function* pickBranchesStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; showTags?: boolean; title: string },
>(
	state: State,
	context: Context,
	{
		filter,
		picked,
		placeholder,
		sort,
		titleContext,
	}: {
		filter?: (b: GitBranch) => boolean;
		picked?: string | string[];
		placeholder: string;
		sort?: BranchSortOptions;
		titleContext?: string;
	},
): AsyncStepResultGenerator<GitBranchReference[]> {
	const branches = await getBranches(state.repo, {
		buttons: [QuickCommandButtons.RevealInSideBar],
		filter: filter,
		picked: picked,
		sort: sort,
	});

	const step = QuickCommand.createPickStep<BranchQuickPickItem>({
		multiselect: branches.length !== 0,
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder: branches.length === 0 ? `No branches found in ${state.repo.formattedName}` : placeholder,
		matchOnDetail: true,
		items:
			branches.length === 0
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: branches,
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				void GitActions.Branch.reveal(item, { select: true, focus: false, expand: true });
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async quickpick => {
			if (quickpick.activeItems.length === 0) return;

			await GitActions.Branch.reveal(quickpick.activeItems[0].item, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection.map(i => i.item) : StepResult.Break;
}

export async function* pickBranchOrTagStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; pickCommitForItem?: boolean; showTags?: boolean; title: string },
>(
	state: State,
	context: Context,
	{
		filter,
		picked,
		placeholder,
		titleContext,
		value,
		additionalButtons,
		ranges,
	}: {
		filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
		picked: string | string[] | undefined;
		placeholder: string | ((context: Context) => string);
		titleContext?: string;
		value: string | undefined;
		additionalButtons?: QuickInputButton[];
		ranges?: boolean;
	},
): AsyncStepResultGenerator<GitReference> {
	context.showTags = true;

	const showTagsButton = new QuickCommandButtons.ShowTagsToggle(context.showTags);

	const getBranchesAndOrTagsFn = async () => {
		return getBranchesAndOrTags(state.repo, context.showTags ? ['branches', 'tags'] : ['branches'], {
			buttons:
				typeof context.pickCommitForItem === 'boolean'
					? [QuickCommandButtons.PickCommit, QuickCommandButtons.RevealInSideBar]
					: [QuickCommandButtons.RevealInSideBar],
			filter: filter,
			picked: picked,
			sort: true,
		});
	};
	const branchesAndOrTags = await getBranchesAndOrTagsFn();

	const step = QuickCommand.createPickStep<ReferencesQuickPickItem>({
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder:
			branchesAndOrTags.length === 0
				? `No branches${context.showTags ? ' or tags' : ''} found in ${state.repo.formattedName}`
				: `${typeof placeholder === 'string' ? placeholder : placeholder(context)}${GlyphChars.Space.repeat(
						3,
				  )}(or enter a reference using #)`,
		matchOnDescription: true,
		matchOnDetail: true,
		value: value,
		items:
			branchesAndOrTags.length === 0
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: branchesAndOrTags,
		additionalButtons: [...(additionalButtons ?? []), showTagsButton],
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.PickCommit) {
				context.pickCommitForItem = true;
				return true;
			}

			if (button === QuickCommandButtons.RevealInSideBar) {
				if (GitReference.isBranch(item)) {
					void GitActions.Branch.reveal(item, { select: true, focus: false, expand: true });
				} else if (GitReference.isTag(item)) {
					void GitActions.Tag.reveal(item, { select: true, focus: false, expand: true });
				} else if (GitReference.isRevision(item)) {
					void GitActions.Commit.reveal(item, { select: true, focus: false, expand: true });
				}
			}
			return false;
		},
		onDidClickButton: async (quickpick, button) => {
			if (button === showTagsButton) {
				quickpick.busy = true;
				quickpick.enabled = false;

				try {
					context.showTags = !context.showTags;
					showTagsButton.on = context.showTags;

					const branchesAndOrTags = await getBranchesAndOrTagsFn();
					quickpick.placeholder =
						branchesAndOrTags.length === 0
							? `${state.repo.formattedName} has no branches${context.showTags ? ' or tags' : ''}`
							: `${
									typeof placeholder === 'string' ? placeholder : placeholder(context)
							  }${GlyphChars.Space.repeat(3)}(or enter a reference using #)`;
					quickpick.items = branchesAndOrTags;
				} finally {
					quickpick.busy = false;
					quickpick.enabled = true;
				}
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: quickpick => {
			if (quickpick.activeItems.length === 0) return;

			const item = quickpick.activeItems[0].item;
			if (GitReference.isBranch(item)) {
				void GitActions.Branch.reveal(item, { select: true, focus: false, expand: true });
			} else if (GitReference.isTag(item)) {
				void GitActions.Tag.reveal(item, { select: true, focus: false, expand: true });
			} else if (GitReference.isRevision(item)) {
				void GitActions.Commit.reveal(item, { select: true, focus: false, expand: true });
			}
		},
		onValidateValue: getValidateGitReferenceFn(state.repo, { ranges: ranges }),
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0].item : StepResult.Break;
}

export async function* pickBranchOrTagStepMultiRepo<
	State extends StepState & { repos: Repository[]; reference?: GitReference },
	Context extends { repos: Repository[]; showTags?: boolean; title: string },
>(
	state: State,
	context: Context,
	{
		filter,
		picked,
		placeholder,
		titleContext,
		value,
	}: {
		filter?: { branches?: (b: GitBranch) => boolean; tags?: (t: GitTag) => boolean };
		picked?: string | string[];
		placeholder: string | ((context: Context) => string);
		titleContext?: string;
		value?: string;
	},
): AsyncStepResultGenerator<GitReference> {
	context.showTags = state.repos.length === 1;

	const showTagsButton = new QuickCommandButtons.ShowTagsToggle(context.showTags);

	const getBranchesAndOrTagsFn = () => {
		return getBranchesAndOrTags(state.repos, context.showTags ? ['branches', 'tags'] : ['branches'], {
			buttons: [QuickCommandButtons.RevealInSideBar],
			// Filter out remote branches if we are going to affect multiple repos
			filter: { branches: state.repos.length === 1 ? undefined : b => !b.remote, ...filter },
			picked: picked ?? state.reference?.ref,
			sort: { branches: { orderBy: BranchSorting.DateDesc }, tags: { orderBy: TagSorting.DateDesc } },
		});
	};
	const branchesAndOrTags = await getBranchesAndOrTagsFn();

	const step = QuickCommand.createPickStep<ReferencesQuickPickItem>({
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder:
			branchesAndOrTags.length === 0
				? `No ${state.repos.length === 1 ? '' : 'common '}branches${
						context.showTags ? ' or tags' : ''
				  } found in ${
						state.repos.length === 1 ? state.repos[0].formattedName : `${state.repos.length} repositories`
				  }`
				: `${typeof placeholder === 'string' ? placeholder : placeholder(context)}${GlyphChars.Space.repeat(
						3,
				  )}(or enter a reference using #)`,
		matchOnDescription: true,
		matchOnDetail: true,
		value: value ?? (GitReference.isRevision(state.reference) ? state.reference.ref : undefined),
		items:
			branchesAndOrTags.length === 0
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: branchesAndOrTags,
		additionalButtons: [showTagsButton],
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				if (GitReference.isBranch(item)) {
					void GitActions.Branch.reveal(item, { select: true, focus: false, expand: true });
				} else if (GitReference.isTag(item)) {
					void GitActions.Tag.reveal(item, { select: true, focus: false, expand: true });
				} else if (GitReference.isRevision(item)) {
					void GitActions.Commit.reveal(item, { select: true, focus: false, expand: true });
				}
			}
		},
		onDidClickButton: async (quickpick, button) => {
			if (button === showTagsButton) {
				quickpick.busy = true;
				quickpick.enabled = false;

				try {
					context.showTags = !context.showTags;
					showTagsButton.on = context.showTags;

					const branchesAndOrTags = await getBranchesAndOrTagsFn();
					quickpick.placeholder =
						branchesAndOrTags.length === 0
							? `No ${state.repos.length === 1 ? '' : 'common '}branches${
									context.showTags ? ' or tags' : ''
							  } found in ${
									state.repos.length === 1
										? state.repos[0].formattedName
										: `${state.repos.length} repositories`
							  }`
							: `${
									typeof placeholder === 'string' ? placeholder : placeholder(context)
							  }${GlyphChars.Space.repeat(3)}(or enter a reference using #)`;
					quickpick.items = branchesAndOrTags;
				} finally {
					quickpick.busy = false;
					quickpick.enabled = true;
				}
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: quickpick => {
			if (quickpick.activeItems.length === 0) return;

			const item = quickpick.activeItems[0].item;
			if (GitReference.isBranch(item)) {
				void GitActions.Branch.reveal(item, { select: true, focus: false, expand: true });
			} else if (GitReference.isTag(item)) {
				void GitActions.Tag.reveal(item, { select: true, focus: false, expand: true });
			} else if (GitReference.isRevision(item)) {
				void GitActions.Commit.reveal(item, { select: true, focus: false, expand: true });
			}
		},
		onValidateValue: getValidateGitReferenceFn(state.repos),
	});

	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0].item : StepResult.Break;
}

export async function* pickCommitStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string },
>(
	state: State,
	context: Context,
	{
		ignoreFocusOut,
		log,
		onDidLoadMore,
		picked,
		placeholder,
		showInSideBarCommand,
		showInSideBarButton: showInSideBar,
		titleContext,
	}: {
		ignoreFocusOut?: boolean;
		log: GitLog | undefined;
		onDidLoadMore?: (log: GitLog | undefined) => void;
		picked?: string | string[] | undefined;
		placeholder: string | ((context: Context, log: GitLog | undefined) => string);
		showInSideBarCommand?: CommandQuickPickItem;
		showInSideBarButton?: {
			button: QuickInputButton;
			onDidClick: (items: Readonly<CommitQuickPickItem<GitCommit>[]>) => void;
		};
		titleContext?: string;
	},
): AsyncStepResultGenerator<GitCommit> {
	function getItems(log: GitLog | undefined) {
		return log == null
			? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
			: [
					...map(log.commits.values(), commit =>
						CommitQuickPickItem.create(
							commit,
							picked != null &&
								(typeof picked === 'string' ? commit.ref === picked : picked.includes(commit.ref)),
							{
								buttons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar],
								compact: true,
								icon: true,
							},
						),
					),
					...(log?.hasMore ? [DirectiveQuickPickItem.create(Directive.LoadMore)] : []),
			  ];
	}

	const step = QuickCommand.createPickStep<CommandQuickPickItem | CommitQuickPickItem>({
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder: typeof placeholder === 'string' ? placeholder : placeholder(context, log),
		ignoreFocusOut: ignoreFocusOut,
		matchOnDescription: true,
		matchOnDetail: true,
		value: typeof picked === 'string' && log?.count === 0 ? picked : undefined,
		items: showInSideBarCommand != null ? [showInSideBarCommand, ...getItems(log)] : getItems(log),
		onDidLoadMore: async quickpick => {
			quickpick.keepScrollPosition = true;
			log = await log?.more?.(configuration.get('advanced.maxListItems'));
			onDidLoadMore?.(log);
			if (typeof placeholder !== 'string') {
				quickpick.placeholder = placeholder(context, log);
			}
			return getItems(log);
		},
		additionalButtons: [
			...(showInSideBar?.button != null ? [showInSideBar?.button] : []),
			...(log?.hasMore ? [QuickCommandButtons.LoadMore] : []),
		],
		onDidClickItemButton: (quickpick, button, item) => {
			if (CommandQuickPickItem.is(item)) return;

			switch (button) {
				case QuickCommandButtons.RevealInSideBar:
					void GitActions.Commit.reveal(item.item, {
						select: true,
						focus: false,
						expand: true,
					});
					break;

				case QuickCommandButtons.SearchInSideBar:
					void Container.instance.searchAndCompareView.search(
						state.repo.path,
						{ pattern: SearchPattern.fromCommit(item.item.ref) },
						{
							label: {
								label: `for ${GitReference.toString(item.item, { icon: false })}`,
							},
							reveal: {
								select: true,
								focus: false,
								expand: true,
							},
						},
					);
					break;
			}
		},
		onDidClickButton: (quickpick, button) => {
			if (log == null) return;

			const items = quickpick.activeItems.filter<CommitQuickPickItem<GitCommit>>(
				(i): i is CommitQuickPickItem<GitCommit> => !CommandQuickPickItem.is(i),
			);

			if (button === showInSideBar?.button) {
				showInSideBar.onDidClick(items);
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async (quickpick, key) => {
			if (quickpick.activeItems.length === 0) return;

			const items = quickpick.activeItems.filter<CommitQuickPickItem<GitCommit>>(
				(i): i is CommitQuickPickItem<GitCommit> => !CommandQuickPickItem.is(i),
			);

			if (key === 'ctrl+right') {
				await GitActions.Commit.reveal(items[0].item, {
					select: true,
					focus: false,
					expand: true,
				});
			} else {
				const commit = items[0].item;
				await Container.instance.searchAndCompareView.search(
					commit.repoPath,
					{ pattern: SearchPattern.fromCommit(commit) },
					{
						label: { label: `for ${GitReference.toString(commit, { icon: false })}` },
						reveal: {
							select: true,
							focus: false,
							expand: true,
						},
					},
				);
			}
		},
		onValidateValue: getValidateGitReferenceFn(state.repo, {
			buttons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar],
		}),
	});
	const selection: StepSelection<typeof step> = yield step;
	if (!QuickCommand.canPickStepContinue(step, state, selection)) return StepResult.Break;

	if (CommandQuickPickItem.is(selection[0])) {
		QuickCommand.endSteps(state);

		await selection[0].execute();
		return StepResult.Break;
	}

	return selection[0].item;
}

export function* pickCommitsStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string },
>(
	state: State,
	context: Context,
	{
		log,
		onDidLoadMore,
		picked,
		placeholder,
		titleContext,
	}: {
		log: GitLog | undefined;
		onDidLoadMore?: (log: GitLog | undefined) => void;
		picked: string | string[] | undefined;
		placeholder: string | ((context: Context, log: GitLog | undefined) => string);
		titleContext?: string;
	},
): StepResultGenerator<GitRevisionReference[]> {
	function getItems(log: GitLog | undefined) {
		return log == null
			? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
			: [
					...map(log.commits.values(), commit =>
						CommitQuickPickItem.create(
							commit,
							picked != null &&
								(typeof picked === 'string' ? commit.ref === picked : picked.includes(commit.ref)),
							{
								buttons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar],
								compact: true,
								icon: true,
							},
						),
					),
					// Since this is multi-select, we can't have a "Load more" item
					// ...(log?.hasMore ? [DirectiveQuickPickItem.create(Directive.LoadMore)] : []),
			  ];
	}

	const step = QuickCommand.createPickStep<CommitQuickPickItem>({
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		multiselect: log != null,
		placeholder: typeof placeholder === 'string' ? placeholder : placeholder(context, log),
		matchOnDescription: true,
		matchOnDetail: true,
		items: getItems(log),
		onDidLoadMore: async quickpick => {
			quickpick.keepScrollPosition = true;
			log = await log?.more?.(configuration.get('advanced.maxListItems'));
			onDidLoadMore?.(log);
			if (typeof placeholder !== 'string') {
				quickpick.placeholder = placeholder(context, log);
			}
			return getItems(log);
		},
		additionalButtons: [...(log?.hasMore ? [QuickCommandButtons.LoadMore] : [])],
		onDidClickItemButton: (quickpick, button, { item }) => {
			switch (button) {
				case QuickCommandButtons.RevealInSideBar:
					void GitActions.Commit.reveal(item, {
						select: true,
						focus: false,
						expand: true,
					});
					break;

				case QuickCommandButtons.SearchInSideBar:
					void Container.instance.searchAndCompareView.search(
						state.repo.path,
						{ pattern: SearchPattern.fromCommit(item.ref) },
						{
							label: {
								label: `for ${GitReference.toString(item, { icon: false })}`,
							},
							reveal: {
								select: true,
								focus: false,
								expand: true,
							},
						},
					);
					break;
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async (quickpick, key) => {
			if (quickpick.activeItems.length === 0) return;

			if (key === 'ctrl+right') {
				await GitActions.Commit.reveal(quickpick.activeItems[0].item, {
					select: true,
					focus: false,
					expand: true,
				});
			} else {
				const commit = quickpick.activeItems[0].item;
				await Container.instance.searchAndCompareView.search(
					commit.repoPath,
					{ pattern: SearchPattern.fromCommit(commit) },
					{
						label: { label: `for ${GitReference.toString(commit, { icon: false })}` },
						reveal: {
							select: true,
							focus: false,
							expand: true,
						},
					},
				);
			}
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection.map(i => i.item) : StepResult.Break;
}

export async function* pickContributorsStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string },
>(
	state: State,
	context: Context,
	placeholder: string = 'Choose contributors',
): AsyncStepResultGenerator<GitContributor[]> {
	const message = (await Container.instance.git.getOrOpenScmRepository(state.repo.path))?.inputBox.value;

	const step = QuickCommand.createPickStep<ContributorQuickPickItem>({
		title: appendReposToTitle(context.title, state, context),
		allowEmpty: true,
		multiselect: true,
		placeholder: placeholder,
		matchOnDescription: true,
		items: (await Container.instance.git.getContributors(state.repo.path)).map(c =>
			ContributorQuickPickItem.create(c, message?.includes(c.getCoauthor()), {
				buttons: [QuickCommandButtons.RevealInSideBar],
			}),
		),
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				void GitActions.Contributor.reveal(item, { select: true, focus: false, expand: true });
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: quickpick => {
			if (quickpick.activeItems.length === 0) return;

			void GitActions.Contributor.reveal(quickpick.activeItems[0].item, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection.map(i => i.item) : StepResult.Break;
}

export async function* pickRepositoryStep<
	State extends PartialStepState & { repo?: string | Repository },
	Context extends { repos: Repository[]; title: string; associatedView: ViewsWithRepositoryFolders },
>(state: State, context: Context, placeholder: string = 'Choose a repository'): AsyncStepResultGenerator<Repository> {
	if (typeof state.repo === 'string') {
		state.repo = Container.instance.git.getRepository(state.repo);
		if (state.repo != null) return state.repo;
	}
	const active = state.repo ?? (await Container.instance.git.getOrOpenRepositoryForEditor());

	const step = QuickCommand.createPickStep<RepositoryQuickPickItem>({
		title: context.title,
		placeholder: placeholder,
		items:
			context.repos.length === 0
				? [DirectiveQuickPickItem.create(Directive.Cancel)]
				: await Promise.all(
						context.repos.map(r =>
							RepositoryQuickPickItem.create(r, r.id === active?.id, {
								branch: true,
								buttons: [QuickCommandButtons.RevealInSideBar],
								fetched: true,
								status: true,
							}),
						),
				  ),
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				void GitActions.Repository.reveal(item.path, context.associatedView, {
					select: true,
					focus: false,
					expand: true,
				});
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: quickpick => {
			if (quickpick.activeItems.length === 0) return;

			void GitActions.Repository.reveal(quickpick.activeItems[0].item.path, context.associatedView, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0].item : StepResult.Break;
}

export async function* pickRepositoriesStep<
	State extends PartialStepState & { repos?: string[] | Repository[] },
	Context extends { repos: Repository[]; title: string; associatedView: ViewsWithRepositoryFolders },
>(
	state: State,
	context: Context,
	options?: { placeholder?: string; skipIfPossible?: boolean },
): AsyncStepResultGenerator<Repository[]> {
	options = { placeholder: 'Choose repositories', skipIfPossible: false, ...options };

	let actives: Repository[];
	if (state.repos != null) {
		if (isStringArray(state.repos)) {
			actives = filterMap(state.repos, path => context.repos.find(r => r.path === path));
			if (options.skipIfPossible && actives.length !== 0 && state.repos.length === actives.length) {
				return actives;
			}
		} else {
			actives = state.repos;
		}
	} else {
		const active = await Container.instance.git.getOrOpenRepositoryForEditor();
		actives = active != null ? [active] : [];
	}

	const step = QuickCommand.createPickStep<RepositoryQuickPickItem>({
		multiselect: true,
		title: context.title,
		placeholder: options.placeholder,
		items:
			context.repos.length === 0
				? [DirectiveQuickPickItem.create(Directive.Cancel)]
				: await Promise.all(
						context.repos.map(repo =>
							RepositoryQuickPickItem.create(
								repo,
								actives.some(r => r.id === repo.id),
								{
									branch: true,
									buttons: [QuickCommandButtons.RevealInSideBar],
									fetched: true,
									status: true,
								},
							),
						),
				  ),
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				void GitActions.Repository.reveal(item.path, context.associatedView, {
					select: true,
					focus: false,
					expand: true,
				});
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: quickpick => {
			if (quickpick.activeItems.length === 0) return;

			void GitActions.Repository.reveal(quickpick.activeItems[0].item.path, context.associatedView, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection.map(i => i.item) : StepResult.Break;
}

export function* pickStashStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string },
>(
	state: State,
	context: Context,
	{
		ignoreFocusOut,
		stash,
		picked,
		placeholder,
		titleContext,
	}: {
		ignoreFocusOut?: boolean;
		stash: GitStash | undefined;
		picked: string | string[] | undefined;
		placeholder: string | ((context: Context, stash: GitStash | undefined) => string);
		titleContext?: string;
	},
): StepResultGenerator<GitStashCommit> {
	const step = QuickCommand.createPickStep<CommitQuickPickItem<GitStashCommit>>({
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder: typeof placeholder === 'string' ? placeholder : placeholder(context, stash),
		ignoreFocusOut: ignoreFocusOut,
		matchOnDescription: true,
		matchOnDetail: true,
		items:
			stash == null
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: [
						...map(stash.commits.values(), commit =>
							CommitQuickPickItem.create(
								commit,
								picked != null &&
									(typeof picked === 'string' ? commit.ref === picked : picked.includes(commit.ref)),
								{
									buttons: [QuickCommandButtons.RevealInSideBar],
									compact: true,
									icon: true,
								},
							),
						),
				  ],
		onDidClickItemButton: (_quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				void GitActions.Stash.reveal(item, {
					select: true,
					focus: false,
					expand: true,
				});
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async quickpick => {
			if (quickpick.activeItems.length === 0) return;

			await GitActions.Stash.reveal(quickpick.activeItems[0].item, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0].item : StepResult.Break;
}

export async function* pickTagsStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; showTags?: boolean; title: string },
>(
	state: State,
	context: Context,
	{
		filter,
		picked,
		placeholder,
		titleContext,
	}: {
		filter?: (b: GitTag) => boolean;
		picked?: string | string[];
		placeholder: string;
		titleContext?: string;
	},
): AsyncStepResultGenerator<GitTagReference[]> {
	const tags = await getTags(state.repo, {
		buttons: [QuickCommandButtons.RevealInSideBar],
		filter: filter,
		picked: picked,
	});

	const step = QuickCommand.createPickStep<TagQuickPickItem>({
		multiselect: tags.length !== 0,
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder: tags.length === 0 ? `No tags found in ${state.repo.formattedName}` : placeholder,
		matchOnDetail: true,
		items:
			tags.length === 0
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: tags,
		onDidClickItemButton: (quickpick, button, { item }) => {
			if (button === QuickCommandButtons.RevealInSideBar) {
				void GitActions.Tag.reveal(item, {
					select: true,
					focus: false,
					expand: true,
				});
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async quickpick => {
			if (quickpick.activeItems.length === 0) return;

			await GitActions.Tag.reveal(quickpick.activeItems[0].item, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection.map(i => i.item) : StepResult.Break;
}

export async function* pickWorktreeStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string; worktrees?: GitWorktree[] },
>(
	state: State,
	context: Context,
	{
		filter,
		includeStatus,
		picked,
		placeholder,
		titleContext,
	}: {
		filter?: (b: GitWorktree) => boolean;
		includeStatus?: boolean;
		picked?: string | string[];
		placeholder: string;
		titleContext?: string;
	},
): AsyncStepResultGenerator<GitWorktree> {
	const worktrees = await getWorktrees(context.worktrees ?? state.repo, {
		buttons: [QuickCommandButtons.OpenInNewWindow, QuickCommandButtons.RevealInSideBar],
		filter: filter,
		includeStatus: includeStatus,
		picked: picked,
	});

	const step = QuickCommand.createPickStep<WorktreeQuickPickItem>({
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder: worktrees.length === 0 ? `No worktrees found in ${state.repo.formattedName}` : placeholder,
		matchOnDetail: true,
		items:
			worktrees.length === 0
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: worktrees,
		onDidClickItemButton: (quickpick, button, { item }) => {
			switch (button) {
				case QuickCommandButtons.OpenInNewWindow:
					void GitActions.Worktree.open(item, { location: OpenWorkspaceLocation.NewWindow });
					break;
				case QuickCommandButtons.RevealInSideBar:
					void GitActions.Worktree.reveal(item, { select: true, focus: false, expand: true });
					break;
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async quickpick => {
			if (quickpick.activeItems.length === 0) return;

			await GitActions.Worktree.reveal(quickpick.activeItems[0].item, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0].item : StepResult.Break;
}

export async function* pickWorktreesStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string; worktrees?: GitWorktree[] },
>(
	state: State,
	context: Context,
	{
		filter,
		includeStatus,
		picked,
		placeholder,
		titleContext,
	}: {
		filter?: (b: GitWorktree) => boolean;
		includeStatus?: boolean;
		picked?: string | string[];
		placeholder: string;
		titleContext?: string;
	},
): AsyncStepResultGenerator<GitWorktree[]> {
	const worktrees = await getWorktrees(context.worktrees ?? state.repo, {
		buttons: [QuickCommandButtons.OpenInNewWindow, QuickCommandButtons.RevealInSideBar],
		filter: filter,
		includeStatus: includeStatus,
		picked: picked,
	});

	const step = QuickCommand.createPickStep<WorktreeQuickPickItem>({
		multiselect: worktrees.length !== 0,
		title: appendReposToTitle(`${context.title}${titleContext ?? ''}`, state, context),
		placeholder: worktrees.length === 0 ? `No worktrees found in ${state.repo.formattedName}` : placeholder,
		matchOnDetail: true,
		items:
			worktrees.length === 0
				? [DirectiveQuickPickItem.create(Directive.Back, true), DirectiveQuickPickItem.create(Directive.Cancel)]
				: worktrees,
		onDidClickItemButton: (quickpick, button, { item }) => {
			switch (button) {
				case QuickCommandButtons.OpenInNewWindow:
					void GitActions.Worktree.open(item, { location: OpenWorkspaceLocation.NewWindow });
					break;
				case QuickCommandButtons.RevealInSideBar:
					void GitActions.Worktree.reveal(item, { select: true, focus: false, expand: true });
					break;
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async quickpick => {
			if (quickpick.activeItems.length === 0) return;

			await GitActions.Worktree.reveal(quickpick.activeItems[0].item, {
				select: true,
				focus: false,
				expand: true,
			});
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection.map(i => i.item) : StepResult.Break;
}

export async function* showCommitOrStashStep<
	State extends PartialStepState & { repo: Repository; reference: GitCommit | GitStashCommit },
	Context extends { repos: Repository[]; title: string },
>(
	state: State,
	context: Context,
): AsyncStepResultGenerator<CommitFilesQuickPickItem | GitCommandQuickPickItem | CommandQuickPickItem> {
	const step: QuickPickStep<CommitFilesQuickPickItem | GitCommandQuickPickItem | CommandQuickPickItem> =
		QuickCommand.createPickStep({
			title: appendReposToTitle(
				GitReference.toString(state.reference, {
					capitalize: true,
					icon: false,
				}),
				state,
				context,
			),
			placeholder: GitReference.toString(state.reference, { capitalize: true, icon: false }),
			ignoreFocusOut: true,
			items: await getShowCommitOrStashStepItems(state),
			// additionalButtons: GitReference.isStash(state.reference)
			// 	? [QuickCommandButtons.RevealInSideBar]
			// 	: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar],
			onDidClickItemButton: (quickpick, button, _item) => {
				if (button === QuickCommandButtons.SearchInSideBar) {
					void Container.instance.searchAndCompareView.search(
						state.repo.path,
						{ pattern: SearchPattern.fromCommit(state.reference.ref) },
						{
							label: { label: `for ${GitReference.toString(state.reference, { icon: false })}` },
							reveal: {
								select: true,
								focus: false,
								expand: true,
							},
						},
					);

					return;
				}

				if (button === QuickCommandButtons.RevealInSideBar) {
					if (GitReference.isStash(state.reference)) {
						void GitActions.Stash.reveal(state.reference, {
							select: true,
							focus: false,
							expand: true,
						});
					} else {
						void GitActions.Commit.reveal(state.reference, {
							select: true,
							focus: false,
							expand: true,
						});
					}
				}
			},
			keys: ['right', 'alt+right', 'ctrl+right'],
			onDidPressKey: async (quickpick, key) => {
				if (quickpick.activeItems.length === 0) return;

				void (await quickpick.activeItems[0].onDidPressKey(key));
			},
		});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0] : StepResult.Break;
}

async function getShowCommitOrStashStepItems<
	State extends PartialStepState & { repo: Repository; reference: GitCommit | GitStashCommit },
>(state: State): Promise<CommandQuickPickItem[]> {
	const items: (CommandQuickPickItem | QuickPickSeparator)[] = [];

	let unpublished: boolean | undefined;

	if (GitCommit.isStash(state.reference)) {
		items.push(
			QuickPickSeparator.create('Actions'),
			new GitCommandQuickPickItem('Apply Stash...', {
				command: 'stash',
				state: {
					subcommand: 'apply',
					repo: state.repo,
					reference: state.reference,
				},
			}),
			new GitCommandQuickPickItem('Delete Stash...', {
				command: 'stash',
				state: {
					subcommand: 'drop',
					repo: state.repo,
					reference: state.reference,
				},
			}),

			QuickPickSeparator.create(),
			new CommitCopyMessageQuickPickItem(state.reference),
		);
	} else {
		const remotes = await Container.instance.git.getRemotesWithProviders(state.repo.path, { sort: true });
		if (remotes?.length) {
			items.push(
				QuickPickSeparator.create(GitRemote.getHighlanderProviderName(remotes) ?? 'Remote'),
				new OpenRemoteResourceCommandQuickPickItem(remotes, {
					type: RemoteResourceType.Commit,
					sha: state.reference.sha,
				}),
				new CopyRemoteResourceCommandQuickPickItem(remotes, {
					type: RemoteResourceType.Commit,
					sha: state.reference.sha,
				}),
			);
		}

		items.push(QuickPickSeparator.create('Actions'));

		const branch = await Container.instance.git.getBranch(state.repo.path);
		const [branches, published] = await Promise.all([
			branch != null
				? Container.instance.git.getCommitBranches(state.repo.path, state.reference.ref, {
						branch: branch.name,
						commitDate: GitCommit.is(state.reference) ? state.reference.committer.date : undefined,
				  })
				: undefined,
			!branch?.remote && branch?.upstream != null ? state.reference.isPushed() : undefined,
		]);

		const commitOnCurrentBranch = Boolean(branches?.length);
		if (commitOnCurrentBranch) {
			unpublished = !published;
			if (unpublished) {
				// TODO@eamodio Add Undo commit, if HEAD & unpushed

				items.push(
					new GitCommandQuickPickItem('Push to Commit...', {
						command: 'push',
						state: {
							repos: state.repo,
							reference: state.reference,
						},
					}),
				);
			}

			items.push(
				new GitCommandQuickPickItem('Revert Commit...', {
					command: 'revert',
					state: {
						repo: state.repo,
						references: [state.reference],
					},
				}),
				new GitCommandQuickPickItem(`Reset ${branch?.name ?? 'Current Branch'} to Commit...`, {
					command: 'reset',
					state: {
						repo: state.repo,
						reference: state.reference,
					},
				}),
				new GitCommandQuickPickItem(`Reset ${branch?.name ?? 'Current Branch'} to Previous Commit...`, {
					command: 'reset',
					state: {
						repo: state.repo,
						reference: GitReference.create(`${state.reference.ref}^`, state.reference.repoPath, {
							refType: 'revision',
							name: `${state.reference.name}^`,
							message: state.reference.message,
						}),
					},
				}),
			);
		} else {
			items.push(
				new GitCommandQuickPickItem('Cherry Pick Commit...', {
					command: 'cherry-pick',
					state: {
						repo: state.repo,
						references: [state.reference],
					},
				}),
			);
		}

		items.push(
			new GitCommandQuickPickItem(`Rebase ${branch?.name ?? 'Current Branch'} onto Commit...`, {
				command: 'rebase',
				state: {
					repo: state.repo,
					reference: state.reference,
				},
			}),
			new GitCommandQuickPickItem('Switch to Commit...', {
				command: 'switch',
				state: {
					repos: [state.repo],
					reference: state.reference,
				},
			}),

			QuickPickSeparator.create(),
			new GitCommandQuickPickItem('Create Branch at Commit...', {
				command: 'branch',
				state: {
					subcommand: 'create',
					repo: state.repo,
					reference: state.reference,
				},
			}),
			new GitCommandQuickPickItem('Create Tag at Commit...', {
				command: 'tag',
				state: {
					subcommand: 'create',
					repo: state.repo,
					reference: state.reference,
				},
			}),

			QuickPickSeparator.create('Copy'),
			new CommitCopyIdQuickPickItem(state.reference),
			new CommitCopyMessageQuickPickItem(state.reference),
		);
	}

	items.push(
		QuickPickSeparator.create('Open'),
		new CommitOpenAllChangesCommandQuickPickItem(state.reference),
		new CommitOpenAllChangesWithWorkingCommandQuickPickItem(state.reference),
		new CommitOpenAllChangesWithDiffToolCommandQuickPickItem(state.reference),
		QuickPickSeparator.create(),
		new CommitOpenFilesCommandQuickPickItem(state.reference),
		new CommitOpenRevisionsCommandQuickPickItem(state.reference),
	);

	items.push(
		QuickPickSeparator.create('Compare'),
		new CommitCompareWithHEADCommandQuickPickItem(state.reference),
		new CommitCompareWithWorkingCommandQuickPickItem(state.reference),
	);

	items.push(
		QuickPickSeparator.create(),
		new CommitOpenDirectoryCompareCommandQuickPickItem(state.reference),
		new CommitOpenDirectoryCompareWithWorkingCommandQuickPickItem(state.reference),
	);

	items.push(
		QuickPickSeparator.create('Browse'),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { openInNewWindow: false }),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, {
			before: true,
			openInNewWindow: false,
		}),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { openInNewWindow: true }),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, {
			before: true,
			openInNewWindow: true,
		}),
	);

	items.splice(
		0,
		0,
		new CommitFilesQuickPickItem(state.reference, {
			unpublished: unpublished,
			hint: 'Click to see all changed files',
		}),
	);
	return items as CommandQuickPickItem[];
}

export function* showCommitOrStashFilesStep<
	State extends PartialStepState & {
		repo: Repository;
		reference: GitCommit | GitStashCommit;
		fileName?: string | undefined;
	},
	Context extends { repos: Repository[]; title: string },
>(
	state: State,
	context: Context,
	options?: { picked?: string },
): StepResultGenerator<CommitFilesQuickPickItem | CommitFileQuickPickItem> {
	if (state.reference.files == null) {
		debugger;
	}

	const step: QuickPickStep<CommitFilesQuickPickItem | CommitFileQuickPickItem> = QuickCommand.createPickStep({
		title: appendReposToTitle(
			GitReference.toString(state.reference, {
				capitalize: true,
				icon: false,
			}),
			state,
			context,
		),
		placeholder: GitReference.toString(state.reference, { capitalize: true, icon: false }),
		ignoreFocusOut: true,
		items: [
			new CommitFilesQuickPickItem(state.reference, {
				picked: state.fileName == null,
				hint: `Click to see ${GitCommit.isStash(state.reference) ? 'stash' : 'commit'} actions`,
			}),
			QuickPickSeparator.create('Files'),
			...(state.reference.files?.map(
				fs => new CommitFileQuickPickItem(state.reference, fs, options?.picked === fs.path),
			) ?? []),
		] as (CommitFilesQuickPickItem | CommitFileQuickPickItem)[],
		matchOnDescription: true,
		// additionalButtons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar],
		onDidClickItemButton: (quickpick, button, _item) => {
			if (button === QuickCommandButtons.SearchInSideBar) {
				void Container.instance.searchAndCompareView.search(
					state.repo.path,
					{ pattern: SearchPattern.fromCommit(state.reference.ref) },
					{
						label: { label: `for ${GitReference.toString(state.reference, { icon: false })}` },
						reveal: {
							select: true,
							focus: false,
							expand: true,
						},
					},
				);

				return;
			}

			if (button === QuickCommandButtons.RevealInSideBar) {
				if (GitReference.isStash(state.reference)) {
					void GitActions.Stash.reveal(state.reference, {
						select: true,
						focus: false,
						expand: true,
					});
				} else {
					void GitActions.Commit.reveal(state.reference, {
						select: true,
						focus: false,
						expand: true,
					});
				}
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async (quickpick, key) => {
			if (quickpick.activeItems.length === 0) return;

			void (await quickpick.activeItems[0].onDidPressKey(key));
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0] : StepResult.Break;
}

export async function* showCommitOrStashFileStep<
	State extends PartialStepState & {
		repo: Repository;
		reference: GitCommit | GitStashCommit;
		fileName: string;
	},
	Context extends { repos: Repository[]; title: string },
>(state: State, context: Context): AsyncStepResultGenerator<CommandQuickPickItem> {
	const step: QuickPickStep<CommandQuickPickItem> = QuickCommand.createPickStep<CommandQuickPickItem>({
		title: appendReposToTitle(
			GitReference.toString(state.reference, {
				capitalize: true,
				icon: false,
			}),
			state,
			context,
			`${pad(GlyphChars.Dot, 2, 2)}${formatPath(state.fileName, { fileOnly: true })}`,
		),
		placeholder: `${formatPath(state.fileName, {
			relativeTo: state.repo.path,
		})} in ${GitReference.toString(state.reference, {
			icon: false,
		})}`,
		ignoreFocusOut: true,
		items: await getShowCommitOrStashFileStepItems(state),
		matchOnDescription: true,
		// additionalButtons: [QuickCommandButtons.RevealInSideBar, QuickCommandButtons.SearchInSideBar],
		onDidClickItemButton: (quickpick, button, _item) => {
			if (button === QuickCommandButtons.SearchInSideBar) {
				void Container.instance.searchAndCompareView.search(
					state.repo.path,
					{ pattern: SearchPattern.fromCommit(state.reference.ref) },
					{
						label: { label: `for ${GitReference.toString(state.reference, { icon: false })}` },
						reveal: {
							select: true,
							focus: false,
							expand: true,
						},
					},
				);

				return;
			}

			if (button === QuickCommandButtons.RevealInSideBar) {
				if (GitReference.isStash(state.reference)) {
					void GitActions.Stash.reveal(state.reference, {
						select: true,
						focus: false,
						expand: true,
					});
				} else {
					void GitActions.Commit.reveal(state.reference, {
						select: true,
						focus: false,
						expand: true,
					});
				}
			}
		},
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async (quickpick, key) => {
			if (quickpick.activeItems.length === 0) return;

			void (await quickpick.activeItems[0].onDidPressKey(key));
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0] : StepResult.Break;
}

async function getShowCommitOrStashFileStepItems<
	State extends PartialStepState & {
		repo: Repository;
		reference: GitCommit | GitStashCommit;
		fileName: string;
	},
>(state: State) {
	const file = await state.reference.findFile(state.fileName);
	if (file == null) return [];

	const items: (CommandQuickPickItem | QuickPickSeparator)[] = [];

	if (GitCommit.isStash(state.reference)) {
		items.push(
			QuickPickSeparator.create(),
			new CommitCopyMessageQuickPickItem(state.reference),
			QuickPickSeparator.create('Actions'),
			new CommitApplyFileChangesCommandQuickPickItem(state.reference, file),
			new CommitRestoreFileChangesCommandQuickPickItem(state.reference, file),
			QuickPickSeparator.create(),
			new CommitCopyMessageQuickPickItem(state.reference),
		);
	} else {
		const remotes = await Container.instance.git.getRemotesWithProviders(state.repo.path, { sort: true });
		if (remotes?.length) {
			items.push(
				QuickPickSeparator.create(GitRemote.getHighlanderProviderName(remotes) ?? 'Remote'),
				new OpenRemoteResourceCommandQuickPickItem(remotes, {
					type: RemoteResourceType.Revision,
					fileName: state.fileName,
					commit: state.reference,
				}),
				new CopyRemoteResourceCommandQuickPickItem(remotes, {
					type: RemoteResourceType.Revision,
					fileName: state.fileName,
					commit: state.reference,
				}),
				QuickPickSeparator.create(),
				new OpenRemoteResourceCommandQuickPickItem(remotes, {
					type: RemoteResourceType.Commit,
					sha: state.reference.ref,
				}),
				new CopyRemoteResourceCommandQuickPickItem(remotes, {
					type: RemoteResourceType.Commit,
					sha: state.reference.sha,
				}),
			);
		}

		items.push(
			QuickPickSeparator.create('Actions'),
			new CommitApplyFileChangesCommandQuickPickItem(state.reference, file),
			new CommitRestoreFileChangesCommandQuickPickItem(state.reference, file),
			QuickPickSeparator.create('Copy'),
			new CommitCopyIdQuickPickItem(state.reference),
			new CommitCopyMessageQuickPickItem(state.reference),
		);
	}

	items.push(
		QuickPickSeparator.create('Open'),
		new CommitOpenChangesCommandQuickPickItem(state.reference, state.fileName),
		new CommitOpenChangesWithWorkingCommandQuickPickItem(state.reference, state.fileName),
		new CommitOpenChangesWithDiffToolCommandQuickPickItem(state.reference, state.fileName),
		QuickPickSeparator.create(),
	);

	if (file.status !== 'D') {
		items.push(new CommitOpenFileCommandQuickPickItem(state.reference, file));
	}
	items.push(new CommitOpenRevisionCommandQuickPickItem(state.reference, file));

	items.push(
		QuickPickSeparator.create('Compare'),
		new CommitCompareWithHEADCommandQuickPickItem(state.reference),
		new CommitCompareWithWorkingCommandQuickPickItem(state.reference),
	);

	items.push(
		QuickPickSeparator.create('Browse'),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { openInNewWindow: false }),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, {
			before: true,
			openInNewWindow: false,
		}),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, { openInNewWindow: true }),
		new CommitBrowseRepositoryFromHereCommandQuickPickItem(state.reference, {
			before: true,
			openInNewWindow: true,
		}),
	);

	items.splice(
		0,
		0,
		new CommitFilesQuickPickItem(state.reference, { file: file, hint: 'Click to see all changed files' }),
	);
	return items as CommandQuickPickItem[];
}

export function* showRepositoryStatusStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string; status: GitStatus },
>(state: State, context: Context): StepResultGenerator<CommandQuickPickItem> {
	const upstream = context.status.getUpstreamStatus({ expand: true, separator: ', ' });
	const working = context.status.getFormattedDiffStatus({ expand: true, separator: ', ' });
	const step: QuickPickStep<CommandQuickPickItem> = QuickCommand.createPickStep<CommandQuickPickItem>({
		title: appendReposToTitle(context.title, state, context),
		placeholder: `${upstream ? `${upstream}, ${working}` : working}`, //'Changes to be committed',
		ignoreFocusOut: true,
		items: getShowRepositoryStatusStepItems(state, context),
		keys: ['right', 'alt+right', 'ctrl+right'],
		onDidPressKey: async (quickpick, key) => {
			if (quickpick.activeItems.length === 0) return;

			void (await quickpick.activeItems[0].onDidPressKey(key));
		},
	});
	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? selection[0] : StepResult.Break;
}

function getShowRepositoryStatusStepItems<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string; status: GitStatus },
>(state: State, context: Context) {
	const items: (DirectiveQuickPickItem | CommandQuickPickItem)[] = [];

	const computed = context.status.computeWorkingTreeStatus();

	let workingTreeStatus;
	if (computed.staged === 0 && computed.unstaged === 0) {
		workingTreeStatus = 'No working tree changes';
	} else {
		workingTreeStatus = `$(files) ${
			computed.staged ? `${pluralize('staged file', computed.staged)} (${computed.stagedStatus})` : ''
		}${
			computed.unstaged
				? `${computed.staged ? ', ' : ''}${pluralize('unstaged file', computed.unstaged)} (${
						computed.unstagedStatus
				  })`
				: ''
		}`;
	}

	if (context.status.upstream) {
		if (context.status.state.ahead === 0 && context.status.state.behind === 0) {
			items.push(
				DirectiveQuickPickItem.create(Directive.Noop, true, {
					label: `$(git-branch) ${context.status.branch} is up to date with $(git-branch) ${context.status.upstream}`,
					detail: workingTreeStatus,
				}),
			);
		} else if (context.status.state.ahead !== 0 && context.status.state.behind !== 0) {
			items.push(
				DirectiveQuickPickItem.create(Directive.Noop, true, {
					label: `$(git-branch) ${context.status.branch} has diverged from $(git-branch) ${context.status.upstream}`,
					detail: workingTreeStatus,
				}),
			);
		} else if (context.status.state.ahead !== 0) {
			items.push(
				DirectiveQuickPickItem.create(Directive.Noop, true, {
					label: `$(git-branch) ${context.status.branch} is ahead of $(git-branch) ${context.status.upstream}`,
					detail: workingTreeStatus,
				}),
			);
		} else if (context.status.state.behind !== 0) {
			items.push(
				DirectiveQuickPickItem.create(Directive.Noop, true, {
					label: `$(git-branch) ${context.status.branch} is behind $(git-branch) ${context.status.upstream}`,
					detail: workingTreeStatus,
				}),
			);
		}

		if (context.status.state.behind !== 0) {
			items.push(
				new GitCommandQuickPickItem(
					`$(cloud-download) ${pluralize('commit', context.status.state.behind)} behind`,
					{
						command: 'log',
						state: {
							repo: state.repo,
							reference: GitReference.create(
								GitRevision.createRange(context.status.ref, context.status.upstream),
								state.repo.path,
							),
						},
					},
				),
			);
		}

		if (context.status.state.ahead !== 0) {
			items.push(
				new GitCommandQuickPickItem(
					`$(cloud-upload) ${pluralize('commit', context.status.state.ahead)} ahead`,
					{
						command: 'log',
						state: {
							repo: state.repo,
							reference: GitReference.create(
								GitRevision.createRange(context.status.upstream, context.status.ref),
								state.repo.path,
							),
						},
					},
				),
			);
		}
	} else {
		items.push(
			DirectiveQuickPickItem.create(Directive.Noop, true, {
				label: `$(git-branch) ${context.status.branch} has no upstream`,
				detail: workingTreeStatus,
			}),
		);
	}

	if (context.status.files.length) {
		items.push(
			new OpenChangedFilesCommandQuickPickItem(
				computed.stagedAddsAndChanges.concat(computed.unstagedAddsAndChanges),
			),
		);
	}

	if (computed.staged > 0) {
		items.push(
			new OpenChangedFilesCommandQuickPickItem(computed.stagedAddsAndChanges, {
				label: '$(files) Open Staged Files',
			}),
		);
	}

	if (computed.unstaged > 0) {
		items.push(
			new OpenChangedFilesCommandQuickPickItem(computed.unstagedAddsAndChanges, {
				label: '$(files) Open Unstaged Files',
			}),
		);
	}

	if (context.status.files.length) {
		items.push(new CommandQuickPickItem('$(x) Close Unchanged Files', Commands.CloseUnchangedFiles));
	}

	return items;
}

export async function* ensureAccessStep<
	State extends PartialStepState & { repo: Repository },
	Context extends { repos: Repository[]; title: string },
>(state: State, context: Context, feature: PlusFeatures): AsyncStepResultGenerator<void> {
	const access = await Container.instance.git.access(feature, state.repo.path);
	if (access.allowed) return undefined;

	const directives: DirectiveQuickPickItem[] = [];
	let placeholder: string;
	if (access.subscription.current.account?.verified === false) {
		directives.push(DirectiveQuickPickItem.create(Directive.RequiresVerification, true));
		placeholder = 'You must verify your account email address before you can continue';
	} else {
		if (access.subscription.required == null) return undefined;

		if (isSubscriptionPaidPlan(access.subscription.required) && access.subscription.current.account != null) {
			directives.push(DirectiveQuickPickItem.create(Directive.RequiresPaidSubscription, true));
			placeholder = 'GitLens+ features require an upgraded account';
		} else {
			if (
				access.subscription.current.account == null &&
				!isSubscriptionPreviewTrialExpired(access.subscription.current)
			) {
				directives.push(
					DirectiveQuickPickItem.create(Directive.StartPreviewTrial, true),
					DirectiveQuickPickItem.create(Directive.RequiresFreeSubscription),
				);
			} else {
				directives.push(DirectiveQuickPickItem.create(Directive.RequiresFreeSubscription));
			}
			placeholder = 'GitLens+ features require a free account';
		}
	}

	const step = QuickCommand.createPickStep<DirectiveQuickPickItem>({
		title: appendReposToTitle(context.title, state, context),
		placeholder: placeholder,
		items: [...directives, DirectiveQuickPickItem.create(Directive.Cancel)],
	});

	const selection: StepSelection<typeof step> = yield step;
	return QuickCommand.canPickStepContinue(step, state, selection) ? undefined : StepResult.Break;
}


text:
Home Arctic Economy Shipping EU wants ban on heavy fuel in Arctic
EU wants ban on heavy fuel in Arctic
Thomas Nilsen, The Independent Barents Observer
An oil tanker plies Arctic waters. (Thomas Nilsen / The Independent Barents Observer)
In a resolution adopted on Tuesday by 111 votes to 8, with 1 abstention, Members of the European Parliament's Committees on Foreign Affairs and Environment says heavy fuel oil should be banned from all ships navigating Arctic waters.
If put into force, such a ban would dramatically increase the costs of sailings to Arctic Norway, Iceland, Greenland, Russia, Canada, Alaska and trans-Arctic shipments between Europe and Asia via the Northern Sea Route.
Light crude oil is far more expensive than HFO.
Heavy fuel oil has more negative environmental impacts because when burned, emissions sulphur dioxide, nitrogen oxide and black carbon is much higher. A spill with heavy fuel oil in Arctic waters could cause disastrous consequences for marine life, sea mammals and birds because it emulsifies on the ocean surface and is extremely difficult to clean up.
[Push is on to get dirty heavy fuel oil out of Arctic shipping]
The EU resolution will be put to a vote by the full Parliament on March 2.
Environment Committee co-rapporteur Sirpa Pietikainen from Finland said: "The EU needs to have a strong policy for the Arctic co-operation, not least because we are closely linked to the rapidly changing Arctic climate: on one the hand, we are affected by the changes in weather and living conditions caused by melting ice, on the other hand we are among the greatest emitters of pollutants to the Arctic atmosphere."
Both the commission and member states are asked to actively facilitate both the ban on use of heavy-fuel oil and carriage of such fuel on all vessels sailing the Arctic.
The parliamentarians asked the commission to include the environmental and climate risks of the use of heavy fuel oil in its story on the risks that the increase in navigation of the Northern Sea Route would bring.
[Two new Arctic emergency centers to open along Northern Sea Route]
It is expected that new trade routes will opens up between Europe and Asia as the Arctic sea ice melts away in a speed few climate researchers ever have predicted.
Last week, CEO Daniel Skjeldam with the Norwegian cruise-liner Hurtigruten said at the Arctic Frontiers conference in Tromsø that it is high time to ban heavy-fuel oil in the Arctic.
"The use of heavy fuel oil (HFO) has already been banned in the Antarctic, now it's time to ban it in the Arctic as well," Daniel Skjeldam said and called on his own business-colleagues to take responsible actions.
"The shipping industry must be frontrunners in promoting regulations that will secure sustainable Arctic growth."
[OPINION: Arctic states and IMO member nations must back push to get heavy fuel oil out of Arctic shipping]
A spill that damaged the with the black, thick and smelly oil would be a worst case scenario for the region's tourism industry.
"If heavy fuel oil is spilled in cold Arctic waters, it will have larger consequences than anywhere else. The Arctic deserves sustainable growth and innovation, and the industry needs to move first," Skjeldam said.
The European Parliament members state that if it does not prove feasible to ban the heavy-fuel oil from vessels in the Arctic, "the EU should take measures to prohibit the use and carriage of heavy-fuel oil for vessels calling at EU ports."
Barents Observer
Previous articlePush is on to get dirty heavy fuel oil out of Arctic shipping
Next articleNASA research flight around the world probes for Arctic pollution

redpajama_set_name: RedPajamaCommonCrawl


text:
Disney Officially Now Own 21st Century Fox
What's On Disney Plus > Blog > Brands > 20th Century Studios > Disney Officially Now Own 21st Century Fox
It's official, Disney now owns 21st Century Fox and the Walt Disney Company has officially announced what they have brought from Fox and what parts of they didn't, which are now part of Fox Entertainment.
They also updated their website with the following image:
This image shows off existing Disney franchises including Toy Story, Frozen, Star Wars, Marvel, plus its new additions including Avatar, Simpsons, Shape Of Water, Deadpool and Atlanta.
Here is the official Disney release:
With 21st Century Fox's iconic collection of businesses and franchises, Disney will be able to provide more appealing high-quality content and entertainment options to meet growing consumer demand; increase its international footprint; and expand its direct-to-consumer offerings, which include ESPN+ for sports fans, the highly-anticipated Disney+ streaming video-on-demand service launching in late 2019; and Disney and 21st Century Fox's combined ownership stake in Hulu.
"This is an extraordinary and historic moment for us—one that will create significant long-term value for our company and our shareholders," said Robert A. Iger, Chairman and Chief Executive Officer, The Walt Disney Company. "Combining Disney's and 21st Century Fox's wealth of creative content and proven talent creates the preeminent global entertainment company, well positioned to lead in an incredibly dynamic and transformative era."
The acquisition includes 21st Century Fox's renowned film production businesses, including Twentieth Century Fox, Fox Searchlight Pictures, Fox 2000 Pictures, Fox Family and Fox Animation; Fox's television creative units, Twentieth Century Fox Television, FX Productions and Fox21; FX Networks; National Geographic Partners; Fox Networks Group International; Star India; and Fox's interests in Hulu, Tata Sky and Endemol Shine Group. Disney and 21st Century Fox entered into a consent decree with the U.S. Department of Justice last year under which Disney will divest 21st Century Fox's Regional Sports Networks.
Earlier today, 21st Century Fox completed the spin-off of a portfolio of 21st Century Fox's news, sports and broadcast businesses, including the FOX News Channel, FOX Business Network, FOX Broadcasting Company, FOX Sports, FOX Television Stations Group, and sports cable networks FS1, FS2, Fox Deportes and Big Ten Network, and certain other assets and liabilities, into Fox Corporation.
Tags: disney, fox
Which Country Does Disney+ Have The Most Movies & TV Shows On?
Selected Disney+ Subscribers Getting 2021 Recap Emails


Gilmore Girls: A Year in the Life Review: "Fall"
As boring as "Summer" was, "Fall" packs in every bit of plot and detail possible. It's like the first two episodes: full, meaningful and the story line meant something.
Lorelai is off to do her Wild pilgrimage because she needs to figure things out, and it actually works. (Her "pilgrimage" is really a coffee stop and a view of a vista, but it's enough to convince me to take a trip like that.) Also, the park ranger is Peter Krause, who played her brother on Parenthood, which I loved.
Anyway, while she's there, she remembers her father and calls Emily to fix their relationship. It's perfect.
She also goes home early and Luke tells her how much he loves her. I wanted to die, right there (except then it would ruin being with Luke)! This was the romance we wanted, and yes, like Emily, we want them married!
He gives her the ring and their low-key wedding is all planned. Heaven.
Lorelai is stable, and it only took her until her 40s. It seems like rory might be on that same path. She's depressed without Logan and her mom, and here comes the Life and Death Brigade to cheer her up. They go out on a night on the town that the rest of us can only dream of. It was fantastic.
Logan returns for the perfect send off, and it's another episode of "love the one you're with." Odette is an ocean away.
Everything seems fine. Rory's writing her book at Emily's house while she's away in Nantucket, while Lorelai is planning her wedding with Ms. Celine. Can we believe she's still alive?
Jess comes back for the wedding and you can see in his eyes that he still has feelings for Rory. If Logan won't take Rory, then why do we let that ride?
I don't know. Jess wanted her; she said no. Logan wanted her; she said no. Now she's alone and Logan, who once proposed marriage, won't commit to her, and Jess is out of the picture. It's a very Christopher-and-Luke situation.
Which, speaking of, we finally see Christopher, and Rory seems very upset when she speaks to him. It feels like more than research for her book. It feels like she's questioning everything.
Luke and Lorelai decide to have a secret wedding before the real wedding and it feels like a wonderland. It's just everything. It's the perfect happy middle for them.
And as great as everything seems for Lorelai, everything seems like Rory is in the same mixed up place Lorelai was when the series started years ago.
And then the last four words. The discussion with Christopher makes sense, and everything else makes sense, except they didn't give us an ending. Logan is supposedly gone, leaving the door open for Jess—and yet, nothing.


Alvor - Approximately 8 km west of Portimão and directly on an estuary is the former fishing village of Alvor. The village offers original charm with Moorish influences and newer buildings which are interspersed through the maze of cobbled streets in the village. The nearby beach is wide and further east it turns into rocky coves and sandstone cliffs. In the village there are beautiful townhouses and apartments, while villas are a little further out. Alvor is known for its night life as well as many restaurants and bars (with live music).


text:
Iron & Wine Goes Down Smooth And Sweet At The Smith
Ahhh, another evening spent buried in the music at Geneva NY's Smith Opera House. After my freshman encounter with the 120-year old historical theatre building back in 2011, the Smith has(dare I say it) started to feel like home returning just two weeks after seeing the stick of country-folk dynamite that was The Lone Bellow. This time around Geneva had the pleasure of hosting the masterfully versatile band Iron & Wine with opening act The Secret Sisters, which was a treat not only given their respective accolades but because it was an evening truly fit for feeling like you were out beneath the stars.
That little architectural slight-of-hand aside, the Smith Opera House once again proved it's strength as an eclectic venue stop last Saturday night. Acoustics rang with an undeniable sheen out into the expanse of high ceilings, deep balconies and the rich smell of so many histories come and gone within walls like those. So beautifully sprung, yet so quickly evaporated before so many hungry eyes. Environment is everything as I see it, and a theatre like the Smith adds weight to every word, gravity to the melody, and beauty well-worn into the palm of each beat.
That steadying presence was put to good use as the evening led off with The Secret Sisters, AKA Alabama's latest answer to those deceptively upbeat and charismatic singing duos first truly popularized in the 50's and 60's. Real-life sisters Laura and Lydia Rogers brought a comfortably harmonized yet effusive blend of country, gospel, blues-rock and swampy Southern charm to their opening set. Backed by a full supporting cast, the two wove cheerily between lyrical heartaches, jailbreaks, murder ballads and an overall sense of optimism that was one bridge away from a shot of whiskey followed by a lithium chaser. All in all, quite a way to have 40 minutes pass through the mirror.
By comparison I'm not quite sure what Iron & Wine was supposed to live up to exactly, but as he has since the band's inception in 2002, lead man Sam Beam makes up the length and breadth of his own expectations. Accompanied by a versatile backing of guitars, banjo, keyboards, accordion, harmonica and electric ukelele, Beam wove well beyond the origin of his lo-fi folk roots into soul, R&B, pop, jazz and a wealth of catalogue-brightening orchestration. Though often his best moments were still the quietest as the band took a break mid-set and Beam stood alone, capo in hand to field a bevy of(suddenly) enthusiastic song requests. And while he did stumble once or twice with older material, songs like "Such Great Heights" and "Naked As We Came" poured outward with a haunting bliss packed so neatly inside pretty guitar lines and Beam's hushed and yearning vocals.
And although he seemed nervous at times digging through so many of his own songs, Beam was the composite free and easy storyteller both in banter and in lyricisms throughout the night. In a fraction of a moment he'd loosely tease or joke around about how "weird" this was going to get, and in the next he'd fixate the crowd within the capturing rhythms of "Boy With A Coin" and "one for the chair-dancers" with "Grace For Saints and Ramblers". And while I would have much preferred a crowd that felt a BIT less content to move to the music from their seats, the mood was vibrant, lush, and fun to keep the toes actively in rhythm to.
So while I wasn't exactly what you'd call an avid Iron & Wine listener going into last Saturday's evening at the Smith Opera House, their headlining set alongside The Secret Sisters made for one of the top live events I've seen so far in 2014. The historical ambiance, casual atmosphere and mellowing sounds made for a night so pleasant that knowing all the words, was certainly no requirement.
Geneva, NY 14456, USA
alternative, concert, folk, geneva ny, gig, indie, iron & wine, iron and wine, live, music, review, sam beam, smith opera house, the secret sisters, venue
Local Opener Makes Strong Case For Pop Revolution
Admiring The Scenery….


text:
Aim & Topics
EQOL2019 Office
Opening Abstract Submission
Final Announcement
Notifications of acceptance to authors
Opening registration
Early registration Deadline
Registration open till Conference days
Faculty of Sport and Physical Education University of Novi Sad, Serbia
Faculty of Kinesiology, University of Zagreb, Croatia
Faculty of Sport and Physical Education,
University of Montenegro, Montenegro
Faculty of Health Sciences, University of Pécs, Hungary
Faculty of Kinesiology, University of Split, Croatia
Faculty of Education, Josip Juraj Strossmayer University of Osijek, Croatia
Science and Research Centre Koper, Slovenia
Under patronage of
International Association of Sport Kinetics
University of Novi Sad, Serbia
Ministry of Science, Education and Technological Development
Provincial Secretariat for Regional Development, Interregional Cooperation and Local Self-Government
Serbian Academy of Sciences and Arts Branch in Novi Sad
Provincial Secretariat for Higher Education and Scientific Research
European Affairs Fund of Autonomous Province of Vojvodina
DATA STATUS doo. Beograd, Serbia
Copyright © 2019 Faculty of Sport and Physical Education, University of Novi Sad, Serbia


text:
The tragedy of war — quite literally — in this play about Kosovo
By Adam Warner
"The Deer House," a disturbing yet enchanting fairy tale, makes its US debut at the Brooklyn Academy of Music on Oct. 5. If this photo's any evidence, expect a lot of surrealism.
Photo by Miel Verhasselt
It took a very real tragedy to get a group of actors to confront a ruthless world.
In this case, the killing was of the brother of a member of the international troupe Needcompany — a journalist whose slaying in Kosovo is the driving force behind "The Deer House," opening Oct. 5 at the Brooklyn Academy of Music.
"With his death, the war entered the company," said Jan Lauwers, founder and artistic director of Needcompany. "It provided the starting point for a play about a group of theater-makers who are increasingly faced with the harsh reality of the world they travel around in."
The immediacy of the tragic event gave Lauwers motivation to write the twofold piece, which is a mixture of fact and fairy tale.
The main story line, which takes place in a spare rehearsal space, is a tranche de vie of real events the company experienced. The other, set in a fairy-tale world in prehistoric Kosovo, focuses on a family of deer breeders that faces murder, anguish, and vengeance as a civil war closes in on them and a war photographer is forced to kill one of their own.
Underwear, rubber antlers, jumbo ears, group singing, bizarre primeval costumes, warped animal figurines, and nude bodies are all abundant as the two tales intertwine. Text, music, dance, and drama accompany these visuals to create a multilingual and multidisciplinary piece.
"The Deer House" is the third chapter in "Sad Face–Happy Face," Needcompany's theatrical trilogy on human nature. The overarching theme of the collection is mortal perseverance on a merciless planet. In 2004, Lauwers brought the first installment of the series, "Isabella's Room," to BAM's Next Wave Festival. The death of Lauwers's father was the inspiration for that play.
"The trilogy is an ode to humanity," said Lauwers. "Wherever we live, we have one thing in common: we are survivors."
"The Deer House" at BAM Harvey Theater [651 Fulton St. at Rockwell Place in Fort Greene, (718) 636-4100], Oct. 5, 7-9 at 7:30 pm. Tickets $25-$45. For info, visit www.bam.org.


text:
Derby Museums from Home
An End of Year Message
Written by Derby Museums
In 2022 Derby Museums celebrated its tenth anniversary as an independent charitable trust. For the first time in a decade, its three museums have been fully opened to the public. The Museum of Making has garnered a slew of awards and was a finalist in the Art Fund Museum of the Year. Thoughtful, intriguing and fun, activities in our museums help people look differently at the world and make learning enjoyable. We deepened our ties with our community, whether it's been exploring new perspectives in our displays or building community through making. We have shared the stories of refugees, celebrated the life and service of the Queen, charted the experience of the Caribbean community in Derby, and continued to find new ways for young people to be creative and develop their skills.
Our purpose is to protect and encourage more people to learn about Derby's heritage, and perhaps the highlight of the year was the acquisition of the stunning self-portrait of Joseph Wright of Derby. I hope visitors will look into his eyes and see the artist in themselves.
Please spare a few moments to watch this excellent film to remind you of the richness of the work of Derby Museums.
Have a very Happy Christmas.
Tony Butler
Executive Director, Derby Museums
If you have enjoyed Derby Museums this year and are able to consider making a donation, this will help us to continue to conserve our heritage and re-open our doors for future generations. All of your generous donations will be matched pound for pound by the National Lottery Heritage Fund. Donate here.
Secure the Future of Our Heritage
Your donation will help ensure Derby's important heritage can continue to be enjoyed by everyone now, and for future generations to come.
Explore our Collections and Galleries
From the world's smallest engine run using a human hair, to a seven tonne Rolls-Royce Trent 1000 engine, find out more about the collections at the Museum of Making.
logo-accredited-museum
logo-arts-council
logo-derby-city-council
logo-designated-outstanding-collection
English_made_possible_logo_black_JPEG
VAQSA_COLOURplaqueCMYK
MOTY2022_Small_finalist_rgb_Black
Receive news about Derby Museums, exhibitions, events and more.
Help us secure the long term future of Derby Museums.
Derby Museums is a registered charity (no. 1149710) and company limited by guarantee (no. 08036113)