You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

258 line
6.1 KiB

  1. 'use strict';
  2. import {
  3. CancellationToken,
  4. commands,
  5. ConfigurationChangeEvent,
  6. ProgressLocation,
  7. TreeItem,
  8. TreeItemCollapsibleState,
  9. window,
  10. } from 'vscode';
  11. import { configuration, StashesViewConfig, ViewFilesLayout } from '../configuration';
  12. import { Container } from '../container';
  13. import { GitReference, GitStashReference, Repository, RepositoryChange, RepositoryChangeEvent } from '../git/git';
  14. import { GitUri } from '../git/gitUri';
  15. import {
  16. ContextValues,
  17. MessageNode,
  18. RepositoryNode,
  19. StashesNode,
  20. StashNode,
  21. SubscribeableViewNode,
  22. unknownGitUri,
  23. ViewNode,
  24. } from './nodes';
  25. import { debug, gate } from '../system';
  26. import { ViewBase } from './viewBase';
  27. export class StashesRepositoryNode extends SubscribeableViewNode<StashesView> {
  28. protected splatted = true;
  29. private child: StashesNode | undefined;
  30. constructor(uri: GitUri, view: StashesView, parent: ViewNode, public readonly repo: Repository, splatted: boolean) {
  31. super(uri, view, parent);
  32. this.splatted = splatted;
  33. }
  34. get id(): string {
  35. return RepositoryNode.getId(this.repo.path);
  36. }
  37. async getChildren(): Promise<ViewNode[]> {
  38. if (this.child == null) {
  39. this.child = new StashesNode(this.uri, this.view, this, this.repo);
  40. }
  41. return this.child.getChildren();
  42. }
  43. getTreeItem(): TreeItem {
  44. this.splatted = false;
  45. const item = new TreeItem(
  46. this.repo.formattedName ?? this.uri.repoPath ?? '',
  47. TreeItemCollapsibleState.Expanded,
  48. );
  49. item.contextValue = ContextValues.RepositoryFolder;
  50. return item;
  51. }
  52. async getSplattedChild() {
  53. if (this.child == null) {
  54. await this.getChildren();
  55. }
  56. return this.child;
  57. }
  58. @gate()
  59. @debug()
  60. async refresh(reset: boolean = false) {
  61. await this.child?.triggerChange(reset);
  62. await this.ensureSubscription();
  63. }
  64. @debug()
  65. protected subscribe() {
  66. return this.repo.onDidChange(this.onRepositoryChanged, this);
  67. }
  68. @debug({
  69. args: {
  70. 0: (e: RepositoryChangeEvent) =>
  71. `{ repository: ${e.repository?.name ?? ''}, changes: ${e.changes.join()} }`,
  72. },
  73. })
  74. private onRepositoryChanged(e: RepositoryChangeEvent) {
  75. if (e.changed(RepositoryChange.Closed)) {
  76. this.dispose();
  77. void this.parent?.triggerChange(true);
  78. return;
  79. }
  80. if (e.changed(RepositoryChange.Config) || e.changed(RepositoryChange.Stash)) {
  81. void this.triggerChange(true);
  82. }
  83. }
  84. }
  85. export class StashesViewNode extends ViewNode<StashesView> {
  86. protected splatted = true;
  87. private children: StashesRepositoryNode[] | undefined;
  88. constructor(view: StashesView) {
  89. super(unknownGitUri, view);
  90. }
  91. async getChildren(): Promise<ViewNode[]> {
  92. if (this.children == null) {
  93. const repositories = await Container.git.getOrderedRepositories();
  94. if (repositories.length === 0) return [new MessageNode(this.view, this, 'No stashes could be found.')];
  95. const splat = repositories.length === 1;
  96. this.children = repositories.map(
  97. r => new StashesRepositoryNode(GitUri.fromRepoPath(r.path), this.view, this, r, splat),
  98. );
  99. }
  100. if (this.children.length === 1) {
  101. const [child] = this.children;
  102. const stash = await child.repo.getStash();
  103. this.view.title = `Stashes (${stash?.commits.size ?? 0})`;
  104. return child.getChildren();
  105. }
  106. return this.children;
  107. }
  108. getTreeItem(): TreeItem {
  109. const item = new TreeItem('Stashes', TreeItemCollapsibleState.Expanded);
  110. return item;
  111. }
  112. async getSplattedChild() {
  113. if (this.children == null) {
  114. await this.getChildren();
  115. }
  116. return this.children?.length === 1 ? this.children[0] : undefined;
  117. }
  118. @gate()
  119. @debug()
  120. refresh(reset: boolean = false) {
  121. if (reset && this.children != null) {
  122. for (const child of this.children) {
  123. child.dispose();
  124. }
  125. this.children = undefined;
  126. }
  127. }
  128. }
  129. export class StashesView extends ViewBase<StashesViewNode, StashesViewConfig> {
  130. protected readonly configKey = 'stashes';
  131. constructor() {
  132. super('gitlens.views.stashes', 'Stashes');
  133. }
  134. getRoot() {
  135. return new StashesViewNode(this);
  136. }
  137. protected registerCommands() {
  138. void Container.viewCommands;
  139. commands.registerCommand(
  140. this.getQualifiedCommand('copy'),
  141. () => commands.executeCommand('gitlens.views.copy', this.selection),
  142. this,
  143. );
  144. commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this);
  145. commands.registerCommand(
  146. this.getQualifiedCommand('setFilesLayoutToAuto'),
  147. () => this.setFilesLayout(ViewFilesLayout.Auto),
  148. this,
  149. );
  150. commands.registerCommand(
  151. this.getQualifiedCommand('setFilesLayoutToList'),
  152. () => this.setFilesLayout(ViewFilesLayout.List),
  153. this,
  154. );
  155. commands.registerCommand(
  156. this.getQualifiedCommand('setFilesLayoutToTree'),
  157. () => this.setFilesLayout(ViewFilesLayout.Tree),
  158. this,
  159. );
  160. }
  161. protected filterConfigurationChanged(e: ConfigurationChangeEvent) {
  162. const changed = super.filterConfigurationChanged(e);
  163. if (
  164. !changed &&
  165. !configuration.changed(e, 'defaultDateFormat') &&
  166. !configuration.changed(e, 'defaultDateSource') &&
  167. !configuration.changed(e, 'defaultDateStyle') &&
  168. !configuration.changed(e, 'defaultGravatarsStyle')
  169. ) {
  170. return false;
  171. }
  172. return true;
  173. }
  174. findStash(stash: GitStashReference, token?: CancellationToken) {
  175. const repoNodeId = RepositoryNode.getId(stash.repoPath);
  176. return this.findNode(StashNode.getId(stash.repoPath, stash.ref), {
  177. maxDepth: 2,
  178. canTraverse: n => {
  179. if (n instanceof StashesViewNode) return true;
  180. if (n instanceof StashesRepositoryNode) {
  181. return n.id.startsWith(repoNodeId);
  182. }
  183. return false;
  184. },
  185. token: token,
  186. });
  187. }
  188. @gate(() => '')
  189. async revealStash(
  190. stash: GitStashReference,
  191. options?: {
  192. select?: boolean;
  193. focus?: boolean;
  194. expand?: boolean | number;
  195. },
  196. ) {
  197. return window.withProgress(
  198. {
  199. location: ProgressLocation.Notification,
  200. title: `Revealing ${GitReference.toString(stash, { icon: false })} in the side bar...`,
  201. cancellable: true,
  202. },
  203. async (progress, token) => {
  204. const node = await this.findStash(stash, token);
  205. if (node == null) return undefined;
  206. await this.ensureRevealNode(node, options);
  207. return node;
  208. },
  209. );
  210. }
  211. private setFilesLayout(layout: ViewFilesLayout) {
  212. return configuration.updateEffective('views', this.configKey, 'files', 'layout', layout);
  213. }
  214. }