From 2c85af90e1d93f03db553af6605fc1dde262a8dc Mon Sep 17 00:00:00 2001 From: Keith Daulton Date: Fri, 17 Nov 2023 17:02:33 -0500 Subject: [PATCH] Updates commit details to new tree component --- src/webviews/apps/commitDetails/commitDetails.ts | 69 ++- .../commitDetails/components/commit-details-app.ts | 16 + .../commitDetails/components/gl-commit-details.ts | 39 ++ .../commitDetails/components/gl-details-base.ts | 609 ++++++++++++++------- .../commitDetails/components/gl-wip-details.ts | 14 + src/webviews/commitDetails/commitDetailsWebview.ts | 4 +- src/webviews/commitDetails/protocol.ts | 2 +- 7 files changed, 522 insertions(+), 231 deletions(-) diff --git a/src/webviews/apps/commitDetails/commitDetails.ts b/src/webviews/apps/commitDetails/commitDetails.ts index f8be93a..0c93efd 100644 --- a/src/webviews/apps/commitDetails/commitDetails.ts +++ b/src/webviews/apps/commitDetails/commitDetails.ts @@ -27,10 +27,11 @@ import { import type { IpcMessage } from '../../protocol'; import { ExecuteCommandType, onIpc } from '../../protocol'; import { App } from '../shared/appBase'; -import type { FileChangeListItem, FileChangeListItemDetail } from '../shared/components/list/file-change-list-item'; import type { WebviewPane, WebviewPaneExpandedChangeEventDetail } from '../shared/components/webview-pane'; import { DOM } from '../shared/dom'; import type { GlCommitDetailsApp } from './components/commit-details-app'; +import type { GlCommitDetails } from './components/gl-commit-details'; +import type { FileChangeListItemDetail } from './components/gl-details-base'; import './commitDetails.scss'; import '../shared/components/actions/action-item'; import '../shared/components/actions/action-nav'; @@ -61,27 +62,6 @@ export class CommitDetailsApp extends App> { override onBind() { const disposables = [ - DOM.on('file-change-list-item', 'file-open-on-remote', e => - this.onOpenFileOnRemote(e.detail), - ), - DOM.on('file-change-list-item', 'file-open', e => - this.onOpenFile(e.detail), - ), - DOM.on('file-change-list-item', 'file-compare-working', e => - this.onCompareFileWithWorking(e.detail), - ), - DOM.on('file-change-list-item', 'file-compare-previous', e => - this.onCompareFileWithPrevious(e.detail), - ), - DOM.on('file-change-list-item', 'file-more-actions', e => - this.onFileMoreActions(e.detail), - ), - DOM.on('file-change-list-item', 'file-stage', e => - this.onStageFile(e.detail), - ), - DOM.on('file-change-list-item', 'file-unstage', e => - this.onUnstageFile(e.detail), - ), DOM.on('[data-action="commit-actions"]', 'click', e => this.onCommitActions(e)), DOM.on('[data-action="pick-commit"]', 'click', e => this.onPickCommit(e)), DOM.on('[data-action="wip"]', 'click', e => this.onSwitchMode(e, 'wip')), @@ -92,7 +72,7 @@ export class CommitDetailsApp extends App> { DOM.on('[data-action="pin"]', 'click', e => this.onTogglePin(e)), DOM.on('[data-action="back"]', 'click', e => this.onNavigate('back', e)), DOM.on('[data-action="forward"]', 'click', e => this.onNavigate('forward', e)), - DOM.on('[data-action="create-patch"]', 'click', e => this.onCreatePatchFromWip(e)), + DOM.on('[data-action="create-patch"]', 'click', _e => this.onCreatePatchFromWip(true)), DOM.on( '[data-region="rich-pane"]', 'expanded-change', @@ -100,6 +80,33 @@ export class CommitDetailsApp extends App> { ), DOM.on('[data-action="explain-commit"]', 'click', e => this.onExplainCommit(e)), DOM.on('[data-action="switch-ai"]', 'click', e => this.onSwitchAiModel(e)), + DOM.on('gl-wip-details', 'create-patch', e => + this.onCreatePatchFromWip(e.detail.checked), + ), + + DOM.on('gl-commit-details', 'file-open-on-remote', e => + this.onOpenFileOnRemote(e.detail), + ), + DOM.on('gl-commit-details,gl-wip-details', 'file-open', e => + this.onOpenFile(e.detail), + ), + DOM.on('gl-commit-details', 'file-compare-working', e => + this.onCompareFileWithWorking(e.detail), + ), + DOM.on( + 'gl-commit-details,gl-wip-details', + 'file-compare-previous', + e => this.onCompareFileWithPrevious(e.detail), + ), + DOM.on('gl-commit-details', 'file-more-actions', e => + this.onFileMoreActions(e.detail), + ), + DOM.on('gl-wip-details', 'file-stage', e => + this.onStageFile(e.detail), + ), + DOM.on('gl-wip-details', 'file-unstage', e => + this.onUnstageFile(e.detail), + ), ]; return disposables; @@ -153,21 +160,9 @@ export class CommitDetailsApp extends App> { } } - private onCreatePatchFromWip(e: MouseEvent) { + private onCreatePatchFromWip(checked: boolean | 'staged' = true) { if (this.state.wip?.changes == null) return; - - const wipCheckedParam = ((e.target as HTMLElement)?.closest('[data-wip-checked]') as HTMLElement | undefined) - ?.dataset.wipChecked; - let wipChecked: boolean | 'staged'; - if (wipCheckedParam == null) { - wipChecked = true; - } else if (wipCheckedParam === 'staged') { - wipChecked = wipCheckedParam; - } else { - wipChecked = wipCheckedParam === 'true'; - } - - this.sendCommand(CreatePatchFromWipCommandType, { changes: this.state.wip?.changes, checked: wipChecked }); + this.sendCommand(CreatePatchFromWipCommandType, { changes: this.state.wip?.changes, checked: checked }); } private onCommandClickedCore(action?: string) { diff --git a/src/webviews/apps/commitDetails/components/commit-details-app.ts b/src/webviews/apps/commitDetails/components/commit-details-app.ts index 545b601..1151282 100644 --- a/src/webviews/apps/commitDetails/components/commit-details-app.ts +++ b/src/webviews/apps/commitDetails/components/commit-details-app.ts @@ -68,6 +68,22 @@ export class GlCommitDetailsApp extends LitElement { defineGkElement(Badge); } + private indentPreference = 16; + private updateDocumentProperties() { + const preference = this.state?.preferences?.indent; + if (preference === this.indentPreference) return; + this.indentPreference = preference ?? 16; + + const rootStyle = document.documentElement.style; + rootStyle.setProperty('--gitlens-tree-indent', `${this.indentPreference}px`); + } + + override updated(changedProperties: Map) { + if (changedProperties.has('state')) { + this.updateDocumentProperties(); + } + } + override render() { const wip = this.state?.wip; diff --git a/src/webviews/apps/commitDetails/components/gl-commit-details.ts b/src/webviews/apps/commitDetails/components/gl-commit-details.ts index c3fcb00..c4a07ab 100644 --- a/src/webviews/apps/commitDetails/components/gl-commit-details.ts +++ b/src/webviews/apps/commitDetails/components/gl-commit-details.ts @@ -8,7 +8,9 @@ import type { PullRequestShape } from '../../../../git/models/pullRequest'; import type { Serialized } from '../../../../system/serialize'; import type { State } from '../../../commitDetails/protocol'; import { messageHeadlineSplitterToken } from '../../../commitDetails/protocol'; +import type { TreeItemAction, TreeItemBase } from '../../shared/components/tree/base'; import { uncommittedSha } from '../commitDetails'; +import type { File } from './gl-details-base'; import { GlDetailsBase } from './gl-details-base'; interface ExplainState { @@ -522,4 +524,41 @@ export class GlCommitDetails extends GlDetailsBase { const { added, deleted, changed } = stats.changedFiles; return html``; } + + override getFileActions(_file: File, _options?: Partial): TreeItemAction[] { + const actions = [ + { + icon: 'go-to-file', + label: 'Open file', + action: 'file-open', + }, + ]; + + if (this.isUncommitted) { + return actions; + } + + actions.push({ + icon: 'git-compare', + label: 'Open Changes with Working File', + action: 'file-compare-working', + }); + + if (!this.isStash) { + actions.push( + { + icon: 'globe', + label: 'Open on remote', + action: 'file-open-on-remote', + }, + { + icon: 'ellipsis', + label: 'Show more actions', + action: 'file-more-actions', + }, + ); + } + + return actions; + } } diff --git a/src/webviews/apps/commitDetails/components/gl-details-base.ts b/src/webviews/apps/commitDetails/components/gl-details-base.ts index 2cee5ca..4b63ddf 100644 --- a/src/webviews/apps/commitDetails/components/gl-details-base.ts +++ b/src/webviews/apps/commitDetails/components/gl-details-base.ts @@ -1,16 +1,31 @@ import type { TemplateResult } from 'lit'; import { html, LitElement } from 'lit'; import { property } from 'lit/decorators.js'; -import { ifDefined } from 'lit/directives/if-defined.js'; -import { when } from 'lit/directives/when.js'; +import type { TextDocumentShowOptions } from 'vscode'; import type { HierarchicalItem } from '../../../../system/array'; import { makeHierarchical } from '../../../../system/array'; import type { Preferences, State } from '../../../commitDetails/protocol'; +import type { + TreeItemAction, + TreeItemActionDetail, + TreeItemBase, + TreeItemCheckedDetail, + TreeItemSelectionDetail, + TreeModel, +} from '../../shared/components/tree/base'; +import '../../shared/components/tree/tree-generator'; type Files = Mutable['files']>>; -type File = Files[0]; +export type File = Files[0]; type Mode = 'commit' | 'stash' | 'wip'; +// Can only import types from 'vscode' +const BesideViewColumn = -2; /*ViewColumn.Beside*/ + +export interface FileChangeListItemDetail extends File { + showOptions?: TextDocumentShowOptions; +} + export class GlDetailsBase extends LitElement { readonly tab: 'wip' | 'commit' = 'commit'; @@ -26,165 +41,43 @@ export class GlDetailsBase extends LitElement { @property({ attribute: 'empty-text' }) emptyText? = 'No Files'; - private renderWipCategory(staged = true, hasFiles = true) { - const label = staged ? 'Staged Changes' : 'Unstaged Changes'; - const shareLabel = `Share ${label}`; - return html` - - ${label} - ${when( - this.tab === 'wip', - () => - html` `, - )} - `; + get fileLayout() { + return this.preferences?.files?.layout ?? 'auto'; } - private renderFileList(mode: Mode, files: Files) { - let items; - let classes; - - if (this.isUncommitted) { - items = []; - classes = `indentGuides-${this.preferences?.indentGuides}`; - - const staged = files.filter(f => f.staged); - if (staged.length) { - // items.push(html`Staged Changes`); - items.push(this.renderWipCategory(true, true)); - - for (const f of staged) { - items.push(this.renderFile(mode, f, 2, true)); - } - } - - const unstaged = files.filter(f => !f.staged); - if (unstaged.length) { - // items.push(html`Unstaged Changes`); - items.push(this.renderWipCategory(false, true)); - - for (const f of unstaged) { - items.push(this.renderFile(mode, f, 2, true)); - } - } - } else { - items = files.map(f => this.renderFile(mode, f)); - } - - return html`${items}`; + get isCompact() { + return this.preferences?.files?.compact ?? true; } - private renderFileTree(mode: Mode, files: Files) { - const compact = this.preferences?.files?.compact ?? true; - - let items; - - if (this.isUncommitted) { - items = []; - - const staged = files.filter(f => f.staged); - if (staged.length) { - // items.push(html`Staged Changes`); - items.push(this.renderWipCategory(true, true)); - items.push(...this.renderFileSubtree(mode, staged, 1, compact)); - } - - const unstaged = files.filter(f => !f.staged); - if (unstaged.length) { - // items.push(html`Unstaged Changes`); - items.push(this.renderWipCategory(false, true)); - items.push(...this.renderFileSubtree(mode, unstaged, 1, compact)); - } - } else { - items = this.renderFileSubtree(mode, files, 0, compact); - } - - return html`${items}`; - } - - private renderFileSubtree(mode: Mode, files: Files, rootLevel: number, compact: boolean) { - const tree = makeHierarchical( - files, - n => n.path.split('/'), - (...parts: string[]) => parts.join('/'), - compact, - ); - const flatTree = flattenHeirarchy(tree); - return flatTree.map(({ level, item }) => { - if (item.name === '') return undefined; - - if (item.value == null) { - return html` - - - ${item.name} - - `; - } - - return this.renderFile(mode, item.value, rootLevel + level, true); - }); - } - - private renderFile(mode: Mode, file: File, level: number = 1, tree: boolean = false): TemplateResult<1> { - return html` - - `; + get indentGuides(): 'none' | 'onHover' | 'always' { + return this.preferences?.indentGuides ?? 'none'; } protected renderChangedFiles(mode: Mode, subtitle?: TemplateResult<1>) { - const layout = this.preferences?.files?.layout ?? 'auto'; - + const isTree = this.isTree(this.files?.length ?? 0); let value = 'tree'; let icon = 'list-tree'; let label = 'View as Tree'; - let isTree = false; - if (this.preferences != null && this.files != null) { - if (layout === 'auto') { - isTree = this.files.length > (this.preferences.files?.threshold ?? 5); - } else { - isTree = layout === 'tree'; - } - - switch (layout) { - case 'auto': - value = 'list'; - icon = 'gl-list-auto'; - label = 'View as List'; - break; - case 'list': - value = 'tree'; - icon = 'list-flat'; - label = 'View as Tree'; - break; - case 'tree': - value = 'auto'; - icon = 'list-tree'; - label = 'View as Auto'; - break; - } + switch (this.fileLayout) { + case 'auto': + value = 'list'; + icon = 'gl-list-auto'; + label = 'View as List'; + break; + case 'list': + value = 'tree'; + icon = 'list-flat'; + label = 'View as Tree'; + break; + case 'tree': + value = 'auto'; + icon = 'list-tree'; + label = 'View as Auto'; + break; } + const treeModel = this.createTreeModel(mode, this.files ?? [], isTree, this.isCompact); + return html` Files changed @@ -198,31 +91,7 @@ export class GlDetailsBase extends LitElement { > -
- ${when( - this.files == null, - () => html` -
- -
-
- -
-
- -
- `, - () => - when( - this.files!.length > 0, - () => - isTree - ? this.renderFileTree(mode, this.files!) - : this.renderFileList(mode, this.files!), - () => html`

${this.emptyText}

`, - ), - )} -
+ ${this.renderTreeFileModel(treeModel)}
`; } @@ -240,36 +109,392 @@ export class GlDetailsBase extends LitElement { protected override createRenderRoot() { return this; } -} -function flattenHeirarchy(item: HierarchicalItem, level = 0): { level: number; item: HierarchicalItem }[] { - const flattened: { level: number; item: HierarchicalItem }[] = []; - if (item == null) return flattened; + // Tree Model changes + protected isTree(count: number) { + if (this.fileLayout === 'auto') { + return count > (this.preferences?.files?.threshold ?? 5); + } + return this.fileLayout === 'tree'; + } - flattened.push({ level: level, item: item }); + protected createTreeModel(mode: Mode, files: Files, isTree = false, compact = true): TreeModel[] { + if (!this.isUncommitted) { + return this.createFileTreeModel(mode, files, isTree, compact); + } - if (item.children != null) { - const children = Array.from(item.children.values()); - children.sort((a, b) => { - if (!a.value || !b.value) { - return (a.value ? 1 : -1) - (b.value ? 1 : -1); + const children = []; + const staged: Files = []; + const unstaged: Files = []; + for (const f of files) { + if (f.staged) { + staged.push(f); + } else { + unstaged.push(f); } + } - if (a.relativePath < b.relativePath) { - return -1; + if (staged.length === 0 || unstaged.length === 0) { + children.push(...this.createFileTreeModel(mode, files, isTree, compact)); + } else { + if (staged.length) { + children.push({ + label: 'Staged Changes', + path: '', + level: 1, // isMulti ? 2 : 1, + branch: true, + checkable: false, + expanded: true, + checked: false, // change.checked !== false, + // disableCheck: true, + context: ['staged'], + children: this.createFileTreeModel(mode, staged, isTree, compact, { level: 2 }), + actions: this.getStagedActions(), + }); } - if (a.relativePath > b.relativePath) { - return 1; + if (unstaged.length) { + children.push({ + label: 'Unstaged Changes', + path: '', + level: 1, // isMulti ? 2 : 1, + branch: true, + checkable: false, + expanded: true, + checked: false, // change.checked === true, + context: ['unstaged'], + children: this.createFileTreeModel(mode, unstaged, isTree, compact, { level: 2 }), + actions: this.getUnstagedActions(), + }); } + } + + return children; + } + + protected sortChildren(children: TreeModel[]): TreeModel[] { + children.sort((a, b) => { + if (a.branch && !b.branch) return -1; + if (!a.branch && b.branch) return 1; + + if (a.label < b.label) return -1; + if (a.label > b.label) return 1; return 0; }); - children.forEach(child => { - flattened.push(...flattenHeirarchy(child, level + 1)); + return children; + } + + protected createFileTreeModel( + mode: Mode, + files: Files, + isTree = false, + compact = true, + options: Partial = { level: 1 }, + ): TreeModel[] { + if (options.level === undefined) { + options.level = 1; + } + + if (!files.length) { + return [ + { + label: 'No changes', + path: '', + level: options.level, + branch: false, + checkable: false, + expanded: true, + checked: false, + }, + ]; + } + + const children: TreeModel[] = []; + if (isTree) { + const fileTree = makeHierarchical( + files, + n => n.path.split('/'), + (...parts: string[]) => parts.join('/'), + compact, + ); + if (fileTree.children != null) { + for (const child of fileTree.children.values()) { + const childModel = this.walkFileTree(child, { level: options.level }); + children.push(childModel); + } + } + } else { + for (const file of files) { + const child = this.fileToTreeModel(file, { level: options.level, branch: false }, true); + children.push(child); + } + } + + this.sortChildren(children); + + return children; + } + + protected walkFileTree(item: HierarchicalItem, options: Partial = { level: 1 }): TreeModel { + if (options.level === undefined) { + options.level = 1; + } + + let model: TreeModel; + if (item.value == null) { + model = this.folderToTreeModel(item.name, options); + } else { + model = this.fileToTreeModel(item.value, options); + } + + if (item.children != null) { + const children = []; + for (const child of item.children.values()) { + const childModel = this.walkFileTree(child, { ...options, level: options.level + 1 }); + children.push(childModel); + } + + if (children.length > 0) { + this.sortChildren(children); + model.branch = true; + model.children = children; + } + } + + return model; + } + + protected getStagedActions(_options?: Partial): TreeItemAction[] { + if (this.tab === 'wip') { + return [ + { + icon: 'gl-cloud-patch-share', + label: 'Share Staged Changes', + action: 'staged-create-patch', + }, + ]; + } + return []; + } + + protected getUnstagedActions(_options?: Partial): TreeItemAction[] { + if (this.tab === 'wip') { + return [ + { + icon: 'gl-cloud-patch-share', + label: 'Share Unstaged Changes', + action: 'unstaged-create-patch', + }, + ]; + } + return []; + } + + protected getFileActions(_file: File, _options?: Partial): TreeItemAction[] { + return []; + } + + protected fileToTreeModel( + file: File, + options?: Partial, + flat = false, + glue = '/', + ): TreeModel { + const pathIndex = file.path.lastIndexOf(glue); + const fileName = pathIndex !== -1 ? file.path.substring(pathIndex + 1) : file.path; + const filePath = flat && pathIndex !== -1 ? file.path.substring(0, pathIndex) : ''; + + return { + branch: false, + expanded: true, + path: file.path, + level: 1, + checkable: false, + checked: false, + icon: 'file', //{ type: 'status', name: file.status }, + label: fileName, + description: flat === true ? filePath : undefined, + context: [file], + actions: this.getFileActions(file, options), + decorations: [{ type: 'text', label: file.status }], + ...options, + }; + } + + protected folderToTreeModel(name: string, options?: Partial): TreeModel { + return { + branch: false, + expanded: true, + path: name, + level: 1, + checkable: false, + checked: false, + icon: 'folder', + label: name, + ...options, + }; + } + + protected renderTreeFileModel(treeModel: TreeModel[]) { + return html``; + } + + // Tree Model action events + // protected onTreeItemActionClicked?(_e: CustomEvent): void; + protected onTreeItemActionClicked(e: CustomEvent) { + if (!e.detail.context || !e.detail.action) return; + + const action = e.detail.action; + switch (action.action) { + // stage actions + case 'staged-create-patch': + this.onCreatePatch(e); + break; + case 'unstaged-create-patch': + this.onCreatePatch(e, true); + break; + // file actions + case 'file-open': + this.onOpenFile(e); + break; + case 'file-unstage': + this.onUnstageFile(e); + break; + case 'file-stage': + this.onStageFile(e); + break; + case 'file-compare-working': + this.onCompareWorking(e); + break; + case 'file-open-on-remote': + this.onOpenFileOnRemote(e); + break; + case 'file-more-actions': + this.onMoreActions(e); + break; + } + } + + protected onTreeItemChecked?(_e: CustomEvent): void; + + // protected onTreeItemSelected?(_e: CustomEvent): void; + protected onTreeItemSelected(e: CustomEvent) { + if (!e.detail.context) return; + + this.onComparePrevious(e); + } + onCreatePatch(_e: CustomEvent, isAll = false) { + const event = new CustomEvent('create-patch', { + detail: { + checked: isAll ? true : 'staged', + }, + }); + this.dispatchEvent(event); + } + onOpenFile(e: CustomEvent) { + if (!e.detail.context) return; + + const [file] = e.detail.context; + const event = new CustomEvent('file-open', { + detail: this.getEventDetail(file, { + preview: false, + viewColumn: e.detail.altKey ? BesideViewColumn : undefined, + }), + }); + this.dispatchEvent(event); + } + + onOpenFileOnRemote(e: CustomEvent) { + if (!e.detail.context) return; + + const [file] = e.detail.context; + const event = new CustomEvent('file-open-on-remote', { + detail: this.getEventDetail(file, { + preview: false, + viewColumn: e.detail.altKey ? BesideViewColumn : undefined, + }), + }); + this.dispatchEvent(event); + } + + onCompareWorking(e: CustomEvent) { + if (!e.detail.context) return; + + const [file] = e.detail.context; + const event = new CustomEvent('file-compare-working', { + detail: this.getEventDetail(file, { + preview: false, + viewColumn: e.detail.altKey ? BesideViewColumn : undefined, + }), + }); + this.dispatchEvent(event); + } + + onComparePrevious(e: CustomEvent) { + if (!e.detail.context) return; + + const [file] = e.detail.context; + const event = new CustomEvent('file-compare-previous', { + detail: this.getEventDetail(file, { + preview: false, + viewColumn: e.detail.altKey ? BesideViewColumn : undefined, + }), + }); + this.dispatchEvent(event); + } + + onMoreActions(e: CustomEvent) { + if (!e.detail.context) return; + + const [file] = e.detail.context; + const event = new CustomEvent('file-more-actions', { + detail: this.getEventDetail(file), + }); + this.dispatchEvent(event); + } + + onStageFile(e: CustomEvent) { + if (!e.detail.context) return; + + const [file] = e.detail.context; + const event = new CustomEvent('file-stage', { + detail: this.getEventDetail(file, { + preview: false, + viewColumn: e.detail.altKey ? BesideViewColumn : undefined, + }), }); + this.dispatchEvent(event); } - return flattened; + onUnstageFile(e: CustomEvent) { + if (!e.detail.context) return; + + const [file] = e.detail.context; + const event = new CustomEvent('file-unstage', { + detail: this.getEventDetail(file, { + preview: false, + viewColumn: e.detail.altKey ? BesideViewColumn : undefined, + }), + }); + this.dispatchEvent(event); + } + + private getEventDetail(file: File, showOptions?: TextDocumentShowOptions): FileChangeListItemDetail { + return { + path: file.path, + repoPath: file.repoPath, + status: file.status, + // originalPath: this.originalPath, + staged: file.staged, + showOptions: showOptions, + }; + } } diff --git a/src/webviews/apps/commitDetails/components/gl-wip-details.ts b/src/webviews/apps/commitDetails/components/gl-wip-details.ts index d2d3bba..4739e71 100644 --- a/src/webviews/apps/commitDetails/components/gl-wip-details.ts +++ b/src/webviews/apps/commitDetails/components/gl-wip-details.ts @@ -3,6 +3,8 @@ import { customElement, property } from 'lit/decorators.js'; import { when } from 'lit/directives/when.js'; import { pluralize } from '../../../../system/string'; import type { Wip } from '../../../commitDetails/protocol'; +import type { TreeItemAction, TreeItemBase } from '../../shared/components/tree/base'; +import type { File } from './gl-details-base'; import { GlDetailsBase } from './gl-details-base'; @customElement('gl-wip-details') @@ -76,4 +78,16 @@ export class GlWipDetails extends GlDetailsBase { ${this.renderChangedFiles('wip')} `; } + + override getFileActions(file: File, _options?: Partial): TreeItemAction[] { + const openFile = { + icon: 'go-to-file', + label: 'Open file', + action: 'file-open', + }; + if (file.staged === true) { + return [openFile, { icon: 'remove', label: 'Unstage changes', action: 'file-unstage' }]; + } + return [openFile, { icon: 'plus', label: 'Stage changes', action: 'file-stage' }]; + } } diff --git a/src/webviews/commitDetails/commitDetailsWebview.ts b/src/webviews/commitDetails/commitDetailsWebview.ts index 69d31ea..1f1108e 100644 --- a/src/webviews/commitDetails/commitDetailsWebview.ts +++ b/src/webviews/commitDetails/commitDetailsWebview.ts @@ -399,7 +399,8 @@ export class CommitDetailsWebviewProvider 'views.commitDetails.files', 'views.commitDetails.avatars', ]) || - configuration.changedAny(e, 'workbench.tree.renderIndentGuides') + configuration.changedAny(e, 'workbench.tree.renderIndentGuides') || + configuration.changedAny(e, 'workbench.tree.indent') ) { this.updatePendingContext({ preferences: { @@ -430,6 +431,7 @@ export class CommitDetailsWebviewProvider configuration.getAny( 'workbench.tree.renderIndentGuides', ) ?? 'onHover', + indent: configuration.getAny('workbench.tree.indent'), }; } diff --git a/src/webviews/commitDetails/protocol.ts b/src/webviews/commitDetails/protocol.ts index 7989e44..0a4c103 100644 --- a/src/webviews/commitDetails/protocol.ts +++ b/src/webviews/commitDetails/protocol.ts @@ -37,7 +37,7 @@ export interface Preferences { avatars: boolean; dateFormat: DateTimeFormat | string; files: Config['views']['commitDetails']['files']; - // indent: number; + indent: number | undefined; indentGuides: 'none' | 'onHover' | 'always'; } export type UpdateablePreferences = Partial>;