diff --git a/src/commands/quickPickItems.ts b/src/commands/quickPickItems.ts deleted file mode 100644 index 8705b05..0000000 --- a/src/commands/quickPickItems.ts +++ /dev/null @@ -1,211 +0,0 @@ -'use strict'; -import { commands, QuickPickItem, TextEditor, Uri, window, workspace } from 'vscode'; -import { Commands } from '../commands'; -import { BuiltInCommands } from '../constants'; -import { GitCommit, GitFileStatus, GitFileStatusItem, GitLogCommit, GitUri } from '../gitProvider'; -import * as moment from 'moment'; -import * as path from 'path'; - -const statusOcticonsMap = { - '?': '$(diff-ignored)', - A: '$(diff-added)', - C: '$(diff-added)', - D: '$(diff-removed)', - M: '$(diff-modified)', - R: '$(diff-renamed)', - U: '$(question)' -}; -function getStatusIcon(status: GitFileStatus, missing: string = '\u00a0\u00a0\u00a0\u00a0'): string { - return statusOcticonsMap[status] || missing; -} - -export interface PartialQuickPickItem { - label?: string; - description?: string; - detail?: string; -} - -export class CommandQuickPickItem implements QuickPickItem { - - label: string; - description: string; - detail: string; - - constructor(item: QuickPickItem, protected command: Commands, protected args?: any[]) { - Object.assign(this, item); - } - - execute(): Thenable<{}> { - return commands.executeCommand(this.command, ...(this.args || [])); - } -} - -export class OpenFilesCommandQuickPickItem extends CommandQuickPickItem { - - constructor(public fileNames: string[], public repoPath: string, item: QuickPickItem) { - super(item, undefined, undefined); - } - - getUri(fileName: string) { - return Uri.file(path.resolve(this.repoPath, fileName)); - } - - async execute(): Promise<{}> { - for (const fileName of this.fileNames) { - this.open(fileName); - } - return undefined; - } - - async open(fileName: string): Promise { - try { - const uri = this.getUri(fileName); - const document = await workspace.openTextDocument(uri); - return window.showTextDocument(document, 1, true); - } - catch (ex) { - return undefined; - } - } -} - -export class OpenCommitFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem { - - constructor(commit: GitLogCommit, item?: PartialQuickPickItem) { - const repoPath = commit.repoPath; - - item = { - ...{ - label: `$(file-symlink-file) Open Files`, - description: undefined, - detail: `Opens all of the files in commit $(git-commit) ${commit.sha}` - }, - ...item - }; - - super(commit.fileStatuses.map(_ => _.fileName), repoPath, item as QuickPickItem); - } -} - -export class OpenStatusFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem { - - constructor(statuses: GitFileStatusItem[], item?: PartialQuickPickItem) { - const repoPath = statuses.length && statuses[0].repoPath; - const fileNames = statuses.map(_ => _.fileName); - - item = { - ...{ - label: `$(file-symlink-file) Open Files`, - description: undefined, - detail: `Opens all of the changed files in the repository` - }, - ...item - }; - - super(fileNames, repoPath, item as QuickPickItem); - } -} - -export class OpenFileCommandQuickPickItem extends CommandQuickPickItem { - - constructor(public fileName: string, public repoPath: string, item: QuickPickItem) { - super(item, undefined, undefined); - } - - getUri() { - return Uri.file(path.resolve(this.repoPath, this.fileName)); - } - - async execute(): Promise<{}> { - try { - const uri = this.getUri(); - return await commands.executeCommand(BuiltInCommands.Open, uri); - } - catch (ex) { - return undefined; - } - } -} - -export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { - - constructor(commit: GitCommit, item?: PartialQuickPickItem) { - item = { - ...{ - label: `$(file-symlink-file) Open File`, - description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.getFormattedPath()}` - }, - ...item - }; - - super(commit.fileName, commit.repoPath, item as QuickPickItem); - } -} - -export class OpenStatusFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { - - constructor(status: GitFileStatusItem, item?: PartialQuickPickItem) { - let directory = path.dirname(status.fileName); - if (!directory || directory === '.') { - directory = undefined; - } - - const icon = getStatusIcon(status.status); - item = { - ...{ - label: `${status.staged ? '$(check)' : '\u00a0\u00a0\u00a0'}\u00a0\u00a0${icon}\u00a0\u00a0\u00a0${path.basename(status.fileName)}`, - description: directory - }, - ...item - }; - - super(status.fileName, status.repoPath, item as QuickPickItem); - } -} - -export class CommitQuickPickItem implements QuickPickItem { - - label: string; - description: string; - detail: string; - - constructor(public commit: GitCommit, descriptionSuffix: string = '') { - this.label = `${commit.author}, ${moment(commit.date).fromNow()}`; - this.description = `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}${descriptionSuffix}`; - this.detail = commit.message; - } -} - -export class FileQuickPickItem implements QuickPickItem { - - label: string; - description: string; - detail: string; - - sha: string; - uri: GitUri; - - constructor(commit: GitCommit, public fileName: string, public status: GitFileStatus) { - const icon = getStatusIcon(status); - this.label = `${icon} ${path.basename(fileName)}`; - - let directory = path.dirname(fileName); - if (!directory || directory === '.') { - directory = undefined; - } - - this.description = directory; - - this.sha = commit.sha; - this.uri = GitUri.fromUri(Uri.file(path.resolve(commit.repoPath, fileName))); - } - - async preview(): Promise<{}> { - try { - return await commands.executeCommand(BuiltInCommands.Open, this.uri); - } - catch (ex) { - return undefined; - } - } -} \ No newline at end of file diff --git a/src/commands/quickPicks.ts b/src/commands/quickPicks.ts deleted file mode 100644 index 5485493..0000000 --- a/src/commands/quickPicks.ts +++ /dev/null @@ -1,231 +0,0 @@ -'use strict'; -import { Iterables } from '../system'; -import { QuickPickOptions, Uri, window, workspace } from 'vscode'; -import { IAdvancedConfig } from '../configuration'; -import { Commands } from '../commands'; -import GitProvider, { GitCommit, GitFileStatusItem, GitLogCommit, GitUri, IGitLog } from '../gitProvider'; -import { CommandQuickPickItem, CommitQuickPickItem, FileQuickPickItem, OpenCommitFileCommandQuickPickItem, OpenStatusFileCommandQuickPickItem, OpenCommitFilesCommandQuickPickItem, OpenStatusFilesCommandQuickPickItem } from './quickPickItems'; -import * as moment from 'moment'; -import * as path from 'path'; - -function getQuickPickIgnoreFocusOut() { - return !workspace.getConfiguration('gitlens').get('advanced').quickPick.closeOnFocusOut; -} - -export class CommitQuickPick { - - static async show(git: GitProvider, commit: GitCommit, workingFileName: string, uri: Uri, currentCommand?: CommandQuickPickItem, goBackCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = {}): Promise { - const items: CommandQuickPickItem[] = []; - - const workingName = (workingFileName && path.basename(workingFileName)) || path.basename(commit.fileName); - - const isUncommitted = commit.isUncommitted; - if (isUncommitted) { - // Since we can't trust the previous sha on an uncommitted commit, find the last commit for this file - const log = await git.getLogForFile(commit.uri.fsPath, undefined, undefined, undefined, 2); - if (!log) return undefined; - - commit = Iterables.first(log.commits.values()); - } - - if (commit.previousSha) { - items.push(new CommandQuickPickItem({ - label: `$(git-compare) Compare with Previous Commit`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.previousSha} \u00a0 $(git-compare) \u00a0 $(git-commit) ${commit.sha}` - }, Commands.DiffWithPrevious, [commit.uri, commit])); - } - - items.push(new CommandQuickPickItem({ - label: `$(git-compare) Compare with Working Tree`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha} \u00a0 $(git-compare) \u00a0 $(file-text) ${workingName}` - }, Commands.DiffWithWorking, [uri, commit])); - - items.push(new OpenCommitFileCommandQuickPickItem(commit)); - - items.push(new CommandQuickPickItem({ - label: `$(clippy) Copy Commit Sha to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}` - }, Commands.CopyShaToClipboard, [uri, commit.sha])); - - items.push(new CommandQuickPickItem({ - label: `$(clippy) Copy Commit Message to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.message}` - }, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message])); - - items.push(new CommandQuickPickItem({ - label: `$(diff) Show Changed Files`, - description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}`, - detail: `Shows all of the changed files in commit $(git-commit) ${commit.sha}` - }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), commit.sha, undefined, currentCommand])); - - if (options.showFileHistory) { - if (workingFileName) { - items.push(new CommandQuickPickItem({ - label: `$(history) Show File History`, - description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)}`, - detail: `Shows the commit history of the file, starting at the most recent commit` - }, Commands.ShowQuickFileHistory, [commit.uri, undefined, undefined, currentCommand])); - } - - items.push(new CommandQuickPickItem({ - label: `$(history) Show Previous File History`, - description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)}`, - detail: `Shows the previous commit history of the file, starting at $(git-commit) ${commit.sha}` - }, Commands.ShowQuickFileHistory, [new GitUri(commit.uri, commit), undefined, undefined, currentCommand])); - } - - if (goBackCommand) { - items.splice(0, 0, goBackCommand); - } - - return await window.showQuickPick(items, { - matchOnDescription: true, - placeHolder: `${commit.getFormattedPath()} \u2022 ${isUncommitted ? 'Uncommitted \u21E8 ' : '' }${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, - ignoreFocusOut: getQuickPickIgnoreFocusOut() - } as QuickPickOptions); - } -} - -export class CommitFilesQuickPick { - - static async show(commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem): Promise { - const items: (FileQuickPickItem | CommandQuickPickItem)[] = commit.fileStatuses.map(fs => new FileQuickPickItem(commit, fs.fileName, fs.status)); - - items.splice(0, 0, new OpenCommitFilesCommandQuickPickItem(commit)); - - items.splice(1, 0, new CommandQuickPickItem({ - label: `$(clippy) Copy Commit Sha to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}` - }, Commands.CopyShaToClipboard, [uri, commit.sha])); - - items.splice(2, 0, new CommandQuickPickItem({ - label: `$(clippy) Copy Commit Message to Clipboard`, - description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.message}` - }, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message])); - - if (goBackCommand) { - items.splice(0, 0, goBackCommand); - } - - const result = await window.showQuickPick(items, { - matchOnDescription: true, - matchOnDetail: true, - placeHolder: `${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, - ignoreFocusOut: getQuickPickIgnoreFocusOut() - // onDidSelectItem: (item: QuickPickItem) => { - // if (item instanceof FileQuickPickItem) { - // item.preview(); - // } - // } - } as QuickPickOptions); - - return result; - } -} - -export class FileCommitsQuickPick { - - static async show(log: IGitLog, uri: Uri, maxCount: number, defaultMaxCount: number, goBackCommand?: CommandQuickPickItem): Promise { - const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))) as (CommitQuickPickItem | CommandQuickPickItem)[]; - - if (maxCount !== 0 && items.length >= defaultMaxCount) { - items.splice(0, 0, new CommandQuickPickItem({ - label: `$(sync) Show All Commits`, - description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${defaultMaxCount} commits`, - detail: `This may take a while` - }, Commands.ShowQuickFileHistory, [uri, 0, undefined, goBackCommand])); - } - - // Only show the full repo option if we are the root - if (!goBackCommand) { - items.splice(0, 0, new CommandQuickPickItem({ - label: `$(repo) Show Repository History`, - description: null, - detail: 'Shows the commit history of the repository' - }, Commands.ShowQuickRepoHistory, [undefined, undefined, undefined, new CommandQuickPickItem({ - label: `go back \u21A9`, - description: null - }, Commands.ShowQuickFileHistory, [uri, maxCount])])); - } - - if (goBackCommand) { - items.splice(0, 0, goBackCommand); - } - - const commit = Iterables.first(log.commits.values()); - - return await window.showQuickPick(items, { - matchOnDescription: true, - matchOnDetail: true, - placeHolder: commit.getFormattedPath(), - ignoreFocusOut: getQuickPickIgnoreFocusOut() - } as QuickPickOptions); - } -} - -export class RepoCommitsQuickPick { - - static async show(log: IGitLog, uri: Uri, maxCount: number, defaultMaxCount: number, goBackCommand?: CommandQuickPickItem): Promise { - const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c, ` \u2014 ${c.fileName}`))) as (CommitQuickPickItem | CommandQuickPickItem)[]; - - if (maxCount !== 0 && items.length >= defaultMaxCount) { - items.splice(0, 0, new CommandQuickPickItem({ - label: `$(sync) Show All Commits`, - description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${defaultMaxCount} commits`, - detail: `This may take a while` - }, Commands.ShowQuickRepoHistory, [uri, 0, undefined, goBackCommand])); - } - - if (goBackCommand) { - items.splice(0, 0, goBackCommand); - } - - return await window.showQuickPick(items, { - matchOnDescription: true, - matchOnDetail: true, - placeHolder: 'Search by commit message, filename, or sha', - ignoreFocusOut: getQuickPickIgnoreFocusOut() - } as QuickPickOptions); - } -} - -export class RepoStatusesQuickPick { - - static async show(statuses: GitFileStatusItem[], goBackCommand?: CommandQuickPickItem): Promise { - // Sort the status by staged and then filename - statuses.sort((a, b) => (a.staged ? -1 : 1) - (b.staged ? -1 : 1) || a.fileName.localeCompare(b.fileName)); - - const items = Array.from(Iterables.map(statuses, s => new OpenStatusFileCommandQuickPickItem(s))) as (OpenStatusFileCommandQuickPickItem | CommandQuickPickItem)[]; - - if (statuses.some(_ => _.staged)) { - const index = statuses.findIndex(_ => !_.staged); - if (index > -1) { - items.splice(index, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && !_.staged), { - label: `$(file-symlink-file) Open Unstaged Files`, - description: undefined, - detail: `Opens all of the unstaged files in the repository` - })); - - items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && _.staged), { - label: `$(file-symlink-file) Open Staged Files`, - description: undefined, - detail: `Opens all of the staged files in the repository` - })); - } - } - - if (statuses.length) { - items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D'))); - } - - if (goBackCommand) { - items.splice(0, 0, goBackCommand); - } - - return await window.showQuickPick(items, { - matchOnDescription: true, - placeHolder: statuses.length ? 'Repository has changes' : 'Repository has no changes', - ignoreFocusOut: getQuickPickIgnoreFocusOut() - } as QuickPickOptions); - } -} \ No newline at end of file diff --git a/src/commands/showQuickCommitDetails.ts b/src/commands/showQuickCommitDetails.ts index 8ee5356..b253551 100644 --- a/src/commands/showQuickCommitDetails.ts +++ b/src/commands/showQuickCommitDetails.ts @@ -4,8 +4,7 @@ import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from '../commands'; import GitProvider, { GitCommit, GitLogCommit, GitUri } from '../gitProvider'; import { Logger } from '../logger'; -import { CommandQuickPickItem, FileQuickPickItem } from './quickPickItems'; -import { CommitQuickPick, CommitFilesQuickPick } from './quickPicks'; +import { CommandQuickPickItem, CommitFileDetailsQuickPick, CommitDetailsQuickPick, CommitWithFileStatusQuickPickItem } from '../quickPicks/commitDetails'; export default class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { @@ -45,7 +44,7 @@ export default class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { } try { - let pick: FileQuickPickItem | CommandQuickPickItem; + let pick: CommitWithFileStatusQuickPickItem | CommandQuickPickItem; let alreadyPickedCommit = !!commit; let workingFileName: string; if (!alreadyPickedCommit) { @@ -54,7 +53,7 @@ export default class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { commit = Iterables.first(log.commits.values()); - pick = await CommitFilesQuickPick.show(commit as GitLogCommit, uri, goBackCommand); + pick = await CommitDetailsQuickPick.show(commit as GitLogCommit, uri, goBackCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { @@ -79,7 +78,7 @@ export default class ShowQuickCommitDetailsCommand extends ActiveEditorCommand { workingFileName = !workingCommit ? commit.fileName : undefined; } - pick = await CommitQuickPick.show(this.git, commit, workingFileName, uri, + pick = await CommitFileDetailsQuickPick.show(this.git, commit, workingFileName, uri, // Create a command to get back to where we are right now new CommandQuickPickItem({ label: `go back \u21A9`, diff --git a/src/commands/showQuickFileHistory.ts b/src/commands/showQuickFileHistory.ts index ebb9173..026cbad 100644 --- a/src/commands/showQuickFileHistory.ts +++ b/src/commands/showQuickFileHistory.ts @@ -3,8 +3,7 @@ import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from '../commands'; import GitProvider, { GitCommit, GitUri } from '../gitProvider'; import { Logger } from '../logger'; -import { CommandQuickPickItem } from './quickPickItems'; -import { FileCommitsQuickPick } from './quickPicks'; +import { CommandQuickPickItem, FileHistoryQuickPick } from '../quickPicks/fileHistory'; export default class ShowQuickFileHistoryCommand extends ActiveEditorCommand { @@ -32,7 +31,7 @@ export default class ShowQuickFileHistoryCommand extends ActiveEditorCommand { const log = await this.git.getLogForFile(gitUri.fsPath, gitUri.sha, gitUri.repoPath, undefined, maxCount); if (!log) return window.showWarningMessage(`Unable to show file history. File is probably not under source control`); - let pick = await FileCommitsQuickPick.show(log, uri, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); + let pick = await FileHistoryQuickPick.show(log, uri, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { diff --git a/src/commands/showQuickRepoHistory.ts b/src/commands/showQuickRepoHistory.ts index 0dd45ec..4fc80b8 100644 --- a/src/commands/showQuickRepoHistory.ts +++ b/src/commands/showQuickRepoHistory.ts @@ -3,8 +3,7 @@ import { commands, TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from '../commands'; import GitProvider, { GitCommit, GitUri } from '../gitProvider'; import { Logger } from '../logger'; -import { CommandQuickPickItem } from './quickPickItems'; -import { RepoCommitsQuickPick } from './quickPicks'; +import { CommandQuickPickItem, RepoHistoryQuickPick } from '../quickPicks/repoHistory'; export default class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { @@ -41,7 +40,7 @@ export default class ShowQuickRepoHistoryCommand extends ActiveEditorCommand { const log = await this.git.getLogForRepo(repoPath, undefined, maxCount); if (!log) return window.showWarningMessage(`Unable to show repository history`); - const pick = await RepoCommitsQuickPick.show(log, uri, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); + const pick = await RepoHistoryQuickPick.show(log, uri, maxCount, this.git.config.advanced.maxQuickHistory, goBackCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { diff --git a/src/commands/showQuickRepoStatus.ts b/src/commands/showQuickRepoStatus.ts index edd15ea..0602e8d 100644 --- a/src/commands/showQuickRepoStatus.ts +++ b/src/commands/showQuickRepoStatus.ts @@ -3,8 +3,7 @@ import { TextEditor, Uri, window } from 'vscode'; import { ActiveEditorCommand, Commands } from '../commands'; import GitProvider, { GitUri } from '../gitProvider'; import { Logger } from '../logger'; -import { CommandQuickPickItem } from './quickPickItems'; -import { RepoStatusesQuickPick } from './quickPicks'; +import { CommandQuickPickItem, RepoStatusQuickPick } from '../quickPicks/repoStatus'; export default class ShowQuickRepoStatusCommand extends ActiveEditorCommand { @@ -36,22 +35,12 @@ export default class ShowQuickRepoStatusCommand extends ActiveEditorCommand { const statuses = await this.git.getStatusesForRepo(repoPath); if (!statuses) return window.showWarningMessage(`Unable to show repository status`); - const pick = await RepoStatusesQuickPick.show(statuses, goBackCommand); + const pick = await RepoStatusQuickPick.show(statuses, goBackCommand); if (!pick) return undefined; if (pick instanceof CommandQuickPickItem) { return pick.execute(); } - - // commit = pick.commit; - - // return commands.executeCommand(Commands.ShowQuickCommitDetails, - // new GitUri(commit.uri, commit), - // commit.sha, undefined, - // new CommandQuickPickItem({ - // label: `go back \u21A9`, - // description: null - // }, Commands.ShowQuickRepoHistory, [uri, maxCount, undefined, goBackCommand])); } catch (ex) { Logger.error('[GitLens.ShowQuickRepoStatusCommand]', ex); diff --git a/src/git/gitEnrichment.ts b/src/git/gitEnrichment.ts index bf87ee9..b0d8333 100644 --- a/src/git/gitEnrichment.ts +++ b/src/git/gitEnrichment.ts @@ -168,4 +168,17 @@ export class GitFileStatusItem { this.staged = !!indexStatus; this.status = (indexStatus || workTreeStatus || 'U') as GitFileStatus; } +} + +const statusOcticonsMap = { + '?': '$(diff-ignored)', + A: '$(diff-added)', + C: '$(diff-added)', + D: '$(diff-removed)', + M: '$(diff-modified)', + R: '$(diff-renamed)', + U: '$(question)' +}; +export function getGitStatusIcon(status: GitFileStatus, missing: string = '\u00a0\u00a0\u00a0\u00a0'): string { + return statusOcticonsMap[status] || missing; } \ No newline at end of file diff --git a/src/gitProvider.ts b/src/gitProvider.ts index 6756d7c..6a042fb 100644 --- a/src/gitProvider.ts +++ b/src/gitProvider.ts @@ -12,6 +12,7 @@ import * as ignore from 'ignore'; import * as moment from 'moment'; import * as path from 'path'; +export { getGitStatusIcon } from './git/gitEnrichment'; export { Git, GitUri }; export * from './git/git'; diff --git a/src/quickPicks/commitDetails.ts b/src/quickPicks/commitDetails.ts new file mode 100644 index 0000000..da0ae08 --- /dev/null +++ b/src/quickPicks/commitDetails.ts @@ -0,0 +1,146 @@ +'use strict'; +import { Iterables } from '../system'; +import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; +import { Commands } from '../commands'; +import GitProvider, { GitCommit, GitLogCommit, GitUri } from '../gitProvider'; +import { CommitWithFileStatusQuickPickItem } from './gitQuickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks'; +import * as moment from 'moment'; +import * as path from 'path'; + +export { CommandQuickPickItem, CommitWithFileStatusQuickPickItem }; + +export class OpenCommitFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { + + constructor(commit: GitCommit, item?: QuickPickItem) { + const uri = Uri.file(path.resolve(commit.repoPath, commit.fileName)); + super(uri, item || { + label: `$(file-symlink-file) Open Working Tree File`, + description: `\u00a0 \u2014 \u00a0\u00a0 ${commit.getFormattedPath()}` + }); + } +} + +export class OpenCommitFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem { + + constructor(commit: GitLogCommit, item?: QuickPickItem) { + const repoPath = commit.repoPath; + const uris = commit.fileStatuses.map(_ => Uri.file(path.resolve(repoPath, _.fileName))); + super(uris, item || { + label: `$(file-symlink-file) Open Working Tree Files`, + description: undefined, + detail: `Opens all of the files in commit $(git-commit) ${commit.sha}` + }); + } +} + +export class CommitDetailsQuickPick { + + static async show(commit: GitLogCommit, uri: Uri, goBackCommand?: CommandQuickPickItem): Promise { + const items: (CommitWithFileStatusQuickPickItem | CommandQuickPickItem)[] = commit.fileStatuses.map(fs => new CommitWithFileStatusQuickPickItem(commit, fs.fileName, fs.status)); + + items.splice(0, 0, new CommandQuickPickItem({ + label: `$(clippy) Copy Commit Sha to Clipboard`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}` + }, Commands.CopyShaToClipboard, [uri, commit.sha])); + + items.splice(1, 0, new CommandQuickPickItem({ + label: `$(clippy) Copy Commit Message to Clipboard`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.message}` + }, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message])); + + items.splice(2, 0, new OpenCommitFilesCommandQuickPickItem(commit)); + + if (goBackCommand) { + items.splice(0, 0, goBackCommand); + } + + const result = await window.showQuickPick(items, { + matchOnDescription: true, + matchOnDetail: true, + placeHolder: `${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, + ignoreFocusOut: getQuickPickIgnoreFocusOut() + // onDidSelectItem: (item: QuickPickItem) => { + // if (item instanceof FileQuickPickItem) { + // item.preview(); + // } + // } + } as QuickPickOptions); + + return result; + } +} + +export class CommitFileDetailsQuickPick { + + static async show(git: GitProvider, commit: GitCommit, workingFileName: string, uri: Uri, currentCommand?: CommandQuickPickItem, goBackCommand?: CommandQuickPickItem, options: { showFileHistory?: boolean } = {}): Promise { + const items: CommandQuickPickItem[] = []; + + const workingName = (workingFileName && path.basename(workingFileName)) || path.basename(commit.fileName); + + const isUncommitted = commit.isUncommitted; + if (isUncommitted) { + // Since we can't trust the previous sha on an uncommitted commit, find the last commit for this file + const log = await git.getLogForFile(commit.uri.fsPath, undefined, undefined, undefined, 2); + if (!log) return undefined; + + commit = Iterables.first(log.commits.values()); + } + + if (commit.previousSha) { + items.push(new CommandQuickPickItem({ + label: `$(git-compare) Compare with Previous Commit`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.previousSha} \u00a0 $(git-compare) \u00a0 $(git-commit) ${commit.sha}` + }, Commands.DiffWithPrevious, [commit.uri, commit])); + } + + items.push(new CommandQuickPickItem({ + label: `$(git-compare) Compare with Working Tree`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha} \u00a0 $(git-compare) \u00a0 $(file-text) ${workingName}` + }, Commands.DiffWithWorking, [uri, commit])); + + items.push(new CommandQuickPickItem({ + label: `$(diff) Show Changed Files`, + description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}`, + detail: `Shows all of the changed files in commit $(git-commit) ${commit.sha}` + }, Commands.ShowQuickCommitDetails, [new GitUri(commit.uri, commit), commit.sha, undefined, currentCommand])); + + items.push(new CommandQuickPickItem({ + label: `$(clippy) Copy Commit Sha to Clipboard`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}` + }, Commands.CopyShaToClipboard, [uri, commit.sha])); + + items.push(new CommandQuickPickItem({ + label: `$(clippy) Copy Commit Message to Clipboard`, + description: `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.message}` + }, Commands.CopyMessageToClipboard, [uri, commit.sha, commit.message])); + + items.push(new OpenCommitFileCommandQuickPickItem(commit)); + + if (options.showFileHistory) { + if (workingFileName) { + items.push(new CommandQuickPickItem({ + label: `$(history) Show File History`, + description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)}`, + detail: `Shows the commit history of the file, starting at the most recent commit` + }, Commands.ShowQuickFileHistory, [commit.uri, undefined, undefined, currentCommand])); + } + + items.push(new CommandQuickPickItem({ + label: `$(history) Show Previous File History`, + description: undefined, //`\u00a0 \u2014 \u00a0\u00a0 ${path.basename(commit.fileName)}`, + detail: `Shows the previous commit history of the file, starting at $(git-commit) ${commit.sha}` + }, Commands.ShowQuickFileHistory, [new GitUri(commit.uri, commit), undefined, undefined, currentCommand])); + } + + if (goBackCommand) { + items.splice(0, 0, goBackCommand); + } + + return await window.showQuickPick(items, { + matchOnDescription: true, + placeHolder: `${commit.getFormattedPath()} \u2022 ${isUncommitted ? 'Uncommitted \u21E8 ' : '' }${commit.sha} \u2022 ${commit.author}, ${moment(commit.date).fromNow()} \u2022 ${commit.message}`, + ignoreFocusOut: getQuickPickIgnoreFocusOut() + } as QuickPickOptions); + } +} \ No newline at end of file diff --git a/src/quickPicks/fileHistory.ts b/src/quickPicks/fileHistory.ts new file mode 100644 index 0000000..53fbdd2 --- /dev/null +++ b/src/quickPicks/fileHistory.ts @@ -0,0 +1,49 @@ +'use strict'; +import { Iterables } from '../system'; +import { QuickPickOptions, Uri, window } from 'vscode'; +import { Commands } from '../commands'; +import { IGitLog } from '../gitProvider'; +import { CommitQuickPickItem } from './gitQuickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks'; + +export { CommandQuickPickItem, CommitQuickPickItem }; + +export class FileHistoryQuickPick { + + static async show(log: IGitLog, uri: Uri, maxCount: number, defaultMaxCount: number, goBackCommand?: CommandQuickPickItem): Promise { + const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c))) as (CommitQuickPickItem | CommandQuickPickItem)[]; + + if (maxCount !== 0 && items.length >= defaultMaxCount) { + items.splice(0, 0, new CommandQuickPickItem({ + label: `$(sync) Show All Commits`, + description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${defaultMaxCount} commits`, + detail: `This may take a while` + }, Commands.ShowQuickFileHistory, [uri, 0, undefined, goBackCommand])); + } + + // Only show the full repo option if we are the root + if (!goBackCommand) { + items.splice(0, 0, new CommandQuickPickItem({ + label: `$(repo) Show Repository History`, + description: null, + detail: 'Shows the commit history of the repository' + }, Commands.ShowQuickRepoHistory, [undefined, undefined, undefined, new CommandQuickPickItem({ + label: `go back \u21A9`, + description: null + }, Commands.ShowQuickFileHistory, [uri, maxCount])])); + } + + if (goBackCommand) { + items.splice(0, 0, goBackCommand); + } + + const commit = Iterables.first(log.commits.values()); + + return await window.showQuickPick(items, { + matchOnDescription: true, + matchOnDetail: true, + placeHolder: commit.getFormattedPath(), + ignoreFocusOut: getQuickPickIgnoreFocusOut() + } as QuickPickOptions); + } +} \ No newline at end of file diff --git a/src/quickPicks/gitQuickPicks.ts b/src/quickPicks/gitQuickPicks.ts new file mode 100644 index 0000000..d56e02a --- /dev/null +++ b/src/quickPicks/gitQuickPicks.ts @@ -0,0 +1,48 @@ +'use strict'; +import { QuickPickItem, Uri } from 'vscode'; +import { getGitStatusIcon, GitCommit, GitFileStatus, GitUri } from '../gitProvider'; +import { openEditor } from './quickPicks'; +import * as moment from 'moment'; +import * as path from 'path'; + +export class CommitQuickPickItem implements QuickPickItem { + + label: string; + description: string; + detail: string; + + constructor(public commit: GitCommit, descriptionSuffix: string = '') { + this.label = `${commit.author}, ${moment(commit.date).fromNow()}`; + this.description = `\u00a0 \u2014 \u00a0\u00a0 $(git-commit) ${commit.sha}${descriptionSuffix}`; + this.detail = commit.message; + } +} + +export class CommitWithFileStatusQuickPickItem implements QuickPickItem { + + label: string; + description: string; + detail: string; + + sha: string; + uri: GitUri; + + constructor(commit: GitCommit, public fileName: string, public status: GitFileStatus) { + const icon = getGitStatusIcon(status); + this.label = `\u00a0\u00a0\u00a0\u00a0${icon}\u00a0\u00a0 ${path.basename(fileName)}`; + + let directory = path.dirname(fileName); + if (!directory || directory === '.') { + directory = undefined; + } + + this.description = directory; + + this.sha = commit.sha; + this.uri = GitUri.fromUri(Uri.file(path.resolve(commit.repoPath, fileName))); + } + + async preview(): Promise<{}> { + return openEditor(this.uri, true); + } +} \ No newline at end of file diff --git a/src/quickPicks/quickPicks.ts b/src/quickPicks/quickPicks.ts new file mode 100644 index 0000000..315adca --- /dev/null +++ b/src/quickPicks/quickPicks.ts @@ -0,0 +1,69 @@ +'use strict'; +import { commands, QuickPickItem, TextEditor, Uri, window, workspace } from 'vscode'; +import { Commands } from '../commands'; +import { IAdvancedConfig } from '../configuration'; +import { BuiltInCommands } from '../constants'; + +export function getQuickPickIgnoreFocusOut() { + return !workspace.getConfiguration('gitlens').get('advanced').quickPick.closeOnFocusOut; +} + +export async function openEditor(uri: Uri, pinned: boolean = false) { + try { + if (pinned) return await commands.executeCommand(BuiltInCommands.Open, uri); + + const document = await workspace.openTextDocument(uri); + return window.showTextDocument(document, (window.activeTextEditor && window.activeTextEditor.viewColumn) || 1, true); + } + catch (ex) { + return undefined; + } +} + +export class CommandQuickPickItem implements QuickPickItem { + + label: string; + description: string; + detail: string; + + constructor(item: QuickPickItem, protected command: Commands, protected args?: any[]) { + Object.assign(this, item); + } + + execute(): Thenable<{}> { + return commands.executeCommand(this.command, ...(this.args || [])); + } +} + +export class OpenFileCommandQuickPickItem extends CommandQuickPickItem { + + constructor(public uri: Uri, item: QuickPickItem) { + super(item, undefined, undefined); + } + + async execute(): Promise<{}> { + return this.preview(); + } + + async open(): Promise { + return openEditor(this.uri); + } + + async preview(): Promise<{}> { + return openEditor(this.uri, true); + } +} + +export class OpenFilesCommandQuickPickItem extends CommandQuickPickItem { + + constructor(public uris: Uri[], item: QuickPickItem) { + super(item, undefined, undefined); + } + + async execute(): Promise<{}> { + for (const uri of this.uris) { + openEditor(uri); + } + return undefined; + } +} \ No newline at end of file diff --git a/src/quickPicks/repoHistory.ts b/src/quickPicks/repoHistory.ts new file mode 100644 index 0000000..615aaef --- /dev/null +++ b/src/quickPicks/repoHistory.ts @@ -0,0 +1,35 @@ +'use strict'; +import { Iterables } from '../system'; +import { QuickPickOptions, Uri, window } from 'vscode'; +import { Commands } from '../commands'; +import { IGitLog } from '../gitProvider'; +import { CommitQuickPickItem } from './gitQuickPicks'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut } from './quickPicks'; + +export { CommandQuickPickItem, CommitQuickPickItem }; + +export class RepoHistoryQuickPick { + + static async show(log: IGitLog, uri: Uri, maxCount: number, defaultMaxCount: number, goBackCommand?: CommandQuickPickItem): Promise { + const items = Array.from(Iterables.map(log.commits.values(), c => new CommitQuickPickItem(c, ` \u2014 ${c.fileName}`))) as (CommitQuickPickItem | CommandQuickPickItem)[]; + + if (maxCount !== 0 && items.length >= defaultMaxCount) { + items.splice(0, 0, new CommandQuickPickItem({ + label: `$(sync) Show All Commits`, + description: `\u00a0 \u2014 \u00a0\u00a0 Currently only showing the first ${defaultMaxCount} commits`, + detail: `This may take a while` + }, Commands.ShowQuickRepoHistory, [uri, 0, undefined, goBackCommand])); + } + + if (goBackCommand) { + items.splice(0, 0, goBackCommand); + } + + return await window.showQuickPick(items, { + matchOnDescription: true, + matchOnDetail: true, + placeHolder: 'Search by commit message, filename, or sha', + ignoreFocusOut: getQuickPickIgnoreFocusOut() + } as QuickPickOptions); + } +} \ No newline at end of file diff --git a/src/quickPicks/repoStatus.ts b/src/quickPicks/repoStatus.ts new file mode 100644 index 0000000..939e6b5 --- /dev/null +++ b/src/quickPicks/repoStatus.ts @@ -0,0 +1,81 @@ +'use strict'; +import { Iterables } from '../system'; +import { QuickPickItem, QuickPickOptions, Uri, window } from 'vscode'; +import { getGitStatusIcon, GitFileStatusItem } from '../gitProvider'; +import { CommandQuickPickItem, getQuickPickIgnoreFocusOut, OpenFileCommandQuickPickItem, OpenFilesCommandQuickPickItem } from './quickPicks'; +import * as path from 'path'; + +export { CommandQuickPickItem }; + +export class OpenStatusFileCommandQuickPickItem extends OpenFileCommandQuickPickItem { + + constructor(status: GitFileStatusItem, item?: QuickPickItem) { + const uri = Uri.file(path.resolve(status.repoPath, status.fileName)); + const icon = getGitStatusIcon(status.status); + + let directory = path.dirname(status.fileName); + if (!directory || directory === '.') { + directory = undefined; + } + + super(uri, item || { + label: `${status.staged ? '$(check)' : '\u00a0\u00a0\u00a0'}\u00a0\u00a0${icon}\u00a0\u00a0\u00a0${path.basename(status.fileName)}`, + description: directory + }); + } +} + +export class OpenStatusFilesCommandQuickPickItem extends OpenFilesCommandQuickPickItem { + + constructor(statuses: GitFileStatusItem[], item?: QuickPickItem) { + const repoPath = statuses.length && statuses[0].repoPath; + const uris = statuses.map(_ => Uri.file(path.resolve(repoPath, _.fileName))); + + super(uris, item || { + label: `$(file-symlink-file) Open Files`, + description: undefined, + detail: `Opens all of the changed files in the repository` + }); + } +} + +export class RepoStatusQuickPick { + + static async show(statuses: GitFileStatusItem[], goBackCommand?: CommandQuickPickItem): Promise { + // Sort the status by staged and then filename + statuses.sort((a, b) => (a.staged ? -1 : 1) - (b.staged ? -1 : 1) || a.fileName.localeCompare(b.fileName)); + + const items = Array.from(Iterables.map(statuses, s => new OpenStatusFileCommandQuickPickItem(s))) as (OpenStatusFileCommandQuickPickItem | OpenStatusFilesCommandQuickPickItem | CommandQuickPickItem)[]; + + if (statuses.some(_ => _.staged)) { + const index = statuses.findIndex(_ => !_.staged); + if (index > -1) { + items.splice(index, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && !_.staged), { + label: `$(file-symlink-file) Open Unstaged Files`, + description: undefined, + detail: `Opens all of the unstaged files in the repository` + })); + + items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D' && _.staged), { + label: `$(file-symlink-file) Open Staged Files`, + description: undefined, + detail: `Opens all of the staged files in the repository` + })); + } + } + + if (statuses.length) { + items.splice(0, 0, new OpenStatusFilesCommandQuickPickItem(statuses.filter(_ => _.status !== 'D'))); + } + + if (goBackCommand) { + items.splice(0, 0, goBackCommand); + } + + return await window.showQuickPick(items, { + matchOnDescription: true, + placeHolder: statuses.length ? 'Repository has changes' : 'Repository has no changes', + ignoreFocusOut: getQuickPickIgnoreFocusOut() + } as QuickPickOptions); + } +} \ No newline at end of file