diff --git a/images/dark/icon-add.svg b/images/dark/icon-add.svg new file mode 100644 index 0000000..3475c1e --- /dev/null +++ b/images/dark/icon-add.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/images/light/icon-add.svg b/images/light/icon-add.svg new file mode 100644 index 0000000..bdecdb0 --- /dev/null +++ b/images/light/icon-add.svg @@ -0,0 +1 @@ +Layer 1 \ No newline at end of file diff --git a/package.json b/package.json index f403db8..47d2ce5 100644 --- a/package.json +++ b/package.json @@ -948,9 +948,18 @@ "category": "GitLens" }, { + "command": "gitlens.stashDelete", + "title": "Delete Stashed Changes", + "category": "GitLens" + }, + { "command": "gitlens.stashSave", "title": "Stash Changes", - "category": "GitLens" + "category": "GitLens", + "icon": { + "dark": "images/dark/icon-add.svg", + "light": "images/light/icon-add.svg" + } }, { "command": "gitlens.resetSuppressedWarnings", @@ -1312,6 +1321,11 @@ "command": "gitlens.stashExplorer.refresh", "when": "gitlens:enabled && view == gitlens.stashExplorer", "group": "navigation" + }, + { + "command": "gitlens.stashSave", + "when": "gitlens:enabled && view == gitlens.stashExplorer", + "group": "navigation" } ], "view/item/context": [ @@ -1354,6 +1368,16 @@ "command": "gitlens.diffWithWorking", "when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == commit-file", "group": "2_gitlens@2" + }, + { + "command": "gitlens.stashApply", + "when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == stash-commit", + "group": "3_gitlens@1" + }, + { + "command": "gitlens.stashDelete", + "when": "gitlens:enabled && view == gitlens.stashExplorer && viewItem == stash-commit", + "group": "3_gitlens@1" } ] }, diff --git a/src/commands/stashApply.ts b/src/commands/stashApply.ts index 80dc786..db47a1e 100644 --- a/src/commands/stashApply.ts +++ b/src/commands/stashApply.ts @@ -7,6 +7,7 @@ import { GlyphChars } from '../constants'; import { CommitQuickPickItem, StashListQuickPick } from '../quickPicks'; import { Logger } from '../logger'; import { CommandQuickPickItem } from '../quickPicks'; +import { StashCommitNode } from '../views/stashCommitNode'; export interface StashApplyCommandArgs { confirm?: boolean; @@ -22,45 +23,59 @@ export class StashApplyCommand extends Command { super(Commands.StashApply); } - async execute(args: StashApplyCommandArgs = { confirm: true, deleteAfter: false }) { + async execute(args: StashApplyCommandArgs | StashCommitNode = { confirm: true, deleteAfter: false }) { if (!this.git.repoPath) return undefined; - args = { ...args }; - if (args.stashItem === undefined || args.stashItem.stashName === undefined) { - const stash = await this.git.getStashList(this.git.repoPath); - if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`); + if (args instanceof StashCommitNode) { + try { + const ret = await this.git.stashApply(this.git.repoPath, args.commit.stashName, false); + args.refreshNode(); + return ret; + } catch (ex) { + return this._errorHandling(ex); + } + } else { + args = { ...args }; + if (args.stashItem === undefined || args.stashItem.stashName === undefined) { + const stash = await this.git.getStashList(this.git.repoPath); + if (stash === undefined) return window.showInformationMessage(`There are no stashed changes`); - const currentCommand = new CommandQuickPickItem({ - label: `go back ${GlyphChars.ArrowBack}`, - description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to apply stashed changes` - }, Commands.StashApply, [args]); + const currentCommand = new CommandQuickPickItem({ + label: `go back ${GlyphChars.ArrowBack}`, + description: `${Strings.pad(GlyphChars.Dash, 2, 3)} to apply stashed changes` + }, Commands.StashApply, [args]); - const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand); - if (pick === undefined || !(pick instanceof CommitQuickPickItem)) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); + const pick = await StashListQuickPick.show(this.git, stash, 'apply', args.goBackCommand, currentCommand); + if (pick === undefined || !(pick instanceof CommitQuickPickItem)) return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); - args.goBackCommand = currentCommand; - args.stashItem = pick.commit as GitStashCommit; - } + args.goBackCommand = currentCommand; + args.stashItem = pick.commit as GitStashCommit; + } - try { - if (args.confirm) { - const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : args.stashItem.message; - const result = await window.showWarningMessage(`Apply stashed changes '${message}' to your working tree?`, { title: 'Yes, delete after applying' } as MessageItem, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem); - if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); + try { + if (args.confirm) { + const message = args.stashItem.message.length > 80 ? `${args.stashItem.message.substring(0, 80)}${GlyphChars.Ellipsis}` : args.stashItem.message; + const result = await window.showWarningMessage(`Apply stashed changes '${message}' to your working tree?`, { title: 'Yes, delete after applying' } as MessageItem, { title: 'Yes' } as MessageItem, { title: 'No', isCloseAffordance: true } as MessageItem); + if (result === undefined || result.title === 'No') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); - args.deleteAfter = result.title !== 'Yes'; - } + args.deleteAfter = result.title !== 'Yes'; + } - return await this.git.stashApply(this.git.repoPath, args.stashItem.stashName, args.deleteAfter); - } - catch (ex) { - Logger.error(ex, 'StashApplyCommand'); - if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) { - return window.showErrorMessage(`Unable to apply stash. Your working tree changes would be overwritten.`); + return await this.git.stashApply(this.git.repoPath, args.stashItem.stashName, args.deleteAfter); } - else { - return window.showErrorMessage(`Unable to apply stash. See output channel for more details`); + catch (ex) { + return this._errorHandling(ex); } } } + + private _errorHandling(ex: any) { + Logger.error(ex, 'StashApplyCommand'); + if (ex.message.includes('Your local changes to the following files would be overwritten by merge')) { + return window.showErrorMessage(`Unable to apply stash. Your working tree changes would be overwritten.`); + } + else { + return window.showErrorMessage(`Unable to apply stash. See output channel for more details`); + } + } } \ No newline at end of file diff --git a/src/commands/stashDelete.ts b/src/commands/stashDelete.ts index 20c02e1..64185ef 100644 --- a/src/commands/stashDelete.ts +++ b/src/commands/stashDelete.ts @@ -5,6 +5,7 @@ import { GlyphChars } from '../constants'; import { GitService } from '../gitService'; import { Logger } from '../logger'; import { CommandQuickPickItem } from '../quickPicks'; +import { StashCommitNode } from '../views/stashCommitNode'; export interface StashDeleteCommandArgs { confirm?: boolean; @@ -19,8 +20,16 @@ export class StashDeleteCommand extends Command { super(Commands.StashDelete); } - async execute(args: StashDeleteCommandArgs = { confirm: true }) { + async execute(args: StashDeleteCommandArgs | StashCommitNode = { confirm: true }) { if (!this.git.repoPath) return undefined; + let stashCommitNode = undefined; + if (args instanceof StashCommitNode) { + stashCommitNode = args; + args = { + confirm: true, + stashItem: args.commit + }; + } args = { ...args }; if (args.stashItem === undefined || args.stashItem.stashName === undefined) return undefined; @@ -36,7 +45,11 @@ export class StashDeleteCommand extends Command { if (result === undefined || result.title !== 'Yes') return args.goBackCommand === undefined ? undefined : args.goBackCommand.execute(); } - return await this.git.stashDelete(this.git.repoPath, args.stashItem.stashName); + const ret = await this.git.stashDelete(this.git.repoPath, args.stashItem.stashName); + if (stashCommitNode) { + stashCommitNode.refreshNode(); + } + return ret; } catch (ex) { Logger.error(ex, 'StashDeleteCommand'); diff --git a/src/views/explorerNode.ts b/src/views/explorerNode.ts index bc0488a..2c67f03 100644 --- a/src/views/explorerNode.ts +++ b/src/views/explorerNode.ts @@ -1,7 +1,6 @@ 'use strict'; -import { ExtensionContext, TreeItem } from 'vscode'; +import { Event, ExtensionContext, TreeItem} from 'vscode'; import { GitService, GitUri } from '../gitService'; - export declare type ResourceType = 'status' | 'branches' | 'repository' | 'branch-history' | 'file-history' | 'stash-history' | 'commit' | 'stash-commit' | 'commit-file'; export abstract class ExplorerNode { @@ -12,4 +11,7 @@ export abstract class ExplorerNode { abstract getChildren(): ExplorerNode[] | Promise; abstract getTreeItem(): TreeItem | Promise; + + onDidChangeTreeData?: Event; + refreshNode?(): void; } \ No newline at end of file diff --git a/src/views/stashCommitNode.ts b/src/views/stashCommitNode.ts index 356e2df..d41817d 100644 --- a/src/views/stashCommitNode.ts +++ b/src/views/stashCommitNode.ts @@ -1,5 +1,5 @@ 'use strict'; -import { ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; +import { Event, EventEmitter, ExtensionContext, TreeItem, TreeItemCollapsibleState } from 'vscode'; import { CommitFileNode } from './commitFileNode'; import { ExplorerNode, ResourceType } from './explorerNode'; import { CommitFormatter, GitService, GitStashCommit, GitUri } from '../gitService'; @@ -8,6 +8,15 @@ export class StashCommitNode extends ExplorerNode { readonly resourceType: ResourceType = 'stash-commit'; + private _onDidChangeTreeData = new EventEmitter(); + public get onDidChangeTreeData(): Event { + return this._onDidChangeTreeData.event; + } + + public refreshNode() { + this._onDidChangeTreeData.fire(); + } + constructor(public readonly commit: GitStashCommit, context: ExtensionContext, git: GitService) { super(new GitUri(commit.uri, commit), context, git); } diff --git a/src/views/stashExplorer.ts b/src/views/stashExplorer.ts index 489dc9f..db4f23f 100644 --- a/src/views/stashExplorer.ts +++ b/src/views/stashExplorer.ts @@ -2,7 +2,6 @@ import { commands, Event, EventEmitter, ExtensionContext, TreeDataProvider, TreeItem, Uri } from 'vscode'; import { ExplorerNode, StashNode } from './explorerNodes'; import { GitService, GitUri } from '../gitService'; -import { StashCommitNode } from './stashCommitNode'; export * from './explorerNodes'; @@ -10,7 +9,7 @@ export class StashExplorer implements TreeDataProvider { private _node: ExplorerNode; private _onDidChangeTreeData = new EventEmitter(); - public get onDidChangeTreeData(): Event { + public get onDidChangeTreeData(): Event { return this._onDidChangeTreeData.event; } @@ -28,6 +27,11 @@ export class StashExplorer implements TreeDataProvider { } async getTreeItem(node: ExplorerNode): Promise { + if (node.onDidChangeTreeData) { + node.onDidChangeTreeData(() => { + this._onDidChangeTreeData.fire(); + }); + } return node.getTreeItem(); }