@ -1,4 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#fff" fill-opacity=".1" stroke="#C5C5C5" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||
<path fill="#C5C5C5" fill-rule="evenodd" d="M5.47 1.518c-.2.029-.323.283-.252.516.025.082.037.097.248.307l.434.33.402.229s.343.092.234.309c-.022.048-.702 3.532-.702 3.532l-.055.229c-.03.158-.335.453-.335.453l-1.387 1.39s-.349.36-.527.553c-.067.136-.014.356.081.45.125.125-.037.116 1.99.116 2.078 0 1.865.24 1.865.24s.161 4.102.234 4.328c.073.226.525.222.6 0 .065-.222.236-4.328.236-4.328s-.211-.234 1.863-.24c2.03.002 1.862.013 1.996-.117.089-.097.141-.33.075-.455l-.521-.546s-.937-.929-1.4-1.391c-.289-.29-.296-.283-.329-.455a2.352 2.352 0 0 0-.056-.227S9.5 3.324 9.463 3.208c-.1-.216.234-.308.234-.308l.402-.226s.133-.023.434-.333c.212-.212.23-.242.253-.307.07-.235.06-.485-.258-.519-.08-.011-1.3-.116-2.521-.115-1.229.001-2.46.108-2.536.118zM8.347 2.56l.937 4.636s.047.25.111.397c.181.416.725.796.725.796s.669.57.502.625c-.156.068-5.08.067-5.241 0-.167-.057.502-.625.502-.625s.54-.38.724-.796c.066-.147.115-.397.115-.397l.934-4.637s.208-.102.35-.1c.139 0 .341.101.341.101z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,3 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#C5C5C5" fill-rule="evenodd" d="M5.47 1.518c-.2.029-.323.283-.252.516.025.082.037.097.248.307l.434.33.402.229s.343.092.234.309c-.022.048-.702 3.532-.702 3.532l-.055.229c-.03.158-.335.453-.335.453l-1.387 1.39s-.349.36-.527.553c-.067.136-.014.356.081.45.125.125-.037.116 1.99.116 2.078 0 1.865.24 1.865.24s.161 4.102.234 4.328c.073.226.525.222.6 0 .065-.222.236-4.328.236-4.328s-.211-.234 1.863-.24c2.03.002 1.862.013 1.996-.117.089-.097.141-.33.075-.455l-.521-.546s-.937-.929-1.4-1.391c-.289-.29-.296-.283-.329-.455a2.352 2.352 0 0 0-.056-.227S9.5 3.324 9.463 3.208c-.1-.216.234-.308.234-.308l.402-.226s.133-.023.434-.333c.212-.212.23-.242.253-.307.07-.235.06-.485-.258-.519-.08-.011-1.3-.116-2.521-.115-1.229.001-2.46.108-2.536.118zM8.347 2.56l.937 4.636s.047.25.111.397c.181.416.725.796.725.796s.669.57.502.625c-.156.068-5.08.067-5.241 0-.167-.057.502-.625.502-.625s.54-.38.724-.796c.066-.147.115-.397.115-.397l.934-4.637s.208-.102.35-.1c.139 0 .341.101.341.101z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,3 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#C5C5C5" fill-rule="evenodd" d="M4.95.143c-.241.034-.39.341-.305.622.03.098.045.116.299.37l.524.397.484.277s.414.11.283.372c-.027.058-.847 4.26-.847 4.26l-.066.275c-.037.19-.404.547-.404.547L3.246 8.94s-.421.433-.636.666c-.081.164-.017.43.098.542.15.151-.045.14 2.399.14 2.506 0 2.25.29 2.25.29s.194 4.946.281 5.22c.088.272.633.267.724 0 .078-.269.285-5.22.285-5.22s-.255-.282 2.245-.29c2.448.003 2.246.017 2.408-.141.107-.117.17-.397.09-.548-.212-.22-.628-.66-.628-.66s-1.13-1.119-1.687-1.676c-.35-.35-.359-.341-.398-.548a2.82 2.82 0 0 0-.067-.274s-.803-4.12-.847-4.26c-.121-.261.282-.372.282-.372l.485-.273s.16-.027.524-.401c.255-.256.277-.292.304-.37.086-.284.072-.585-.31-.626A35.293 35.293 0 0 0 8.006 0a36.01 36.01 0 0 0-3.058.143zm3.468 1.256l1.13 5.59s.057.301.134.48c.219.501.874.959.874.959s.807.686.606.753c-.189.082-6.126.081-6.32 0-.202-.068.605-.753.605-.753s.652-.46.874-.96c.078-.177.138-.479.138-.479l1.126-5.591s.25-.123.422-.121c.167 0 .411.122.411.122z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,3 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#C5C5C5" fill-rule="evenodd" d="M10.579 1.28c-.195-.145-.517-.034-.655.226-.049.09-.05.114-.05.472l.09.652.146.538s.215.372-.064.463c-.06.022-3.61 2.413-3.61 2.413l-.242.148c-.16.11-.672.102-.672.102l-2.368.003s-.604.008-.92.02c-.174.06-.316.294-.315.454 0 .213-.13.067 1.597 1.795 1.773 1.772 1.386 1.796 1.386 1.796s-3.36 3.635-3.491 3.89c-.131.255.258.637.512.511.244-.134 3.891-3.488 3.891-3.488s.02-.38 1.794 1.382c1.728 1.733 1.576 1.6 1.801 1.602.159-.006.402-.16.453-.322.005-.307.02-.911.02-.911s-.006-1.59-.006-2.379c0-.494-.013-.495.106-.668.042-.062.112-.175.146-.242 0 0 2.346-3.481 2.414-3.612.099-.27.462-.063.462-.063l.536.15s.133.095.654.087c.362 0 .402-.01.477-.046.261-.14.465-.363.223-.663a35.31 35.31 0 0 0-2.052-2.248 36.038 36.038 0 0 0-2.263-2.061zm1.565 3.342L8.99 9.374s-.173.253-.244.434c-.2.51-.06 1.296-.06 1.296s.084 1.056-.105.96c-.192-.074-4.39-4.273-4.47-4.468-.093-.191.962-.105.962-.105s.785.137 1.296-.06c.181-.07.436-.242.436-.242l4.75-3.157s.264.09.384.213c.118.119.205.377.205.377z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,4 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#000" fill-opacity=".1" stroke="#424242" stroke-width=".5" d="M.25.25h15.5v15.5H.25z"/> | |||
<path fill="#424242" fill-rule="evenodd" d="M5.47 1.518c-.2.029-.323.283-.252.516.025.082.037.097.248.307l.434.33.402.229s.343.092.234.309c-.022.048-.702 3.532-.702 3.532l-.055.229c-.03.158-.335.453-.335.453l-1.387 1.39s-.349.36-.527.553c-.067.136-.014.356.081.45.125.125-.037.116 1.99.116 2.078 0 1.865.24 1.865.24s.161 4.102.234 4.328c.073.226.525.222.6 0 .065-.222.236-4.328.236-4.328s-.211-.234 1.863-.24c2.03.002 1.862.013 1.996-.117.089-.097.141-.33.075-.455l-.521-.546s-.937-.929-1.4-1.391c-.289-.29-.296-.283-.329-.455a2.352 2.352 0 0 0-.056-.227S9.5 3.324 9.463 3.208c-.1-.216.234-.308.234-.308l.402-.226s.133-.023.434-.333c.212-.212.23-.242.253-.307.07-.235.06-.485-.258-.519-.08-.011-1.3-.116-2.521-.115-1.229.001-2.46.108-2.536.118zM8.347 2.56l.937 4.636s.047.25.111.397c.181.416.725.796.725.796s.669.57.502.625c-.156.068-5.08.067-5.241 0-.167-.057.502-.625.502-.625s.54-.38.724-.796c.066-.147.115-.397.115-.397l.934-4.637s.208-.102.35-.1c.139 0 .341.101.341.101z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,3 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#424242" fill-rule="evenodd" d="M5.47 1.518c-.2.029-.323.283-.252.516.025.082.037.097.248.307l.434.33.402.229s.343.092.234.309c-.022.048-.702 3.532-.702 3.532l-.055.229c-.03.158-.335.453-.335.453l-1.387 1.39s-.349.36-.527.553c-.067.136-.014.356.081.45.125.125-.037.116 1.99.116 2.078 0 1.865.24 1.865.24s.161 4.102.234 4.328c.073.226.525.222.6 0 .065-.222.236-4.328.236-4.328s-.211-.234 1.863-.24c2.03.002 1.862.013 1.996-.117.089-.097.141-.33.075-.455l-.521-.546s-.937-.929-1.4-1.391c-.289-.29-.296-.283-.329-.455a2.352 2.352 0 0 0-.056-.227S9.5 3.324 9.463 3.208c-.1-.216.234-.308.234-.308l.402-.226s.133-.023.434-.333c.212-.212.23-.242.253-.307.07-.235.06-.485-.258-.519-.08-.011-1.3-.116-2.521-.115-1.229.001-2.46.108-2.536.118zM8.347 2.56l.937 4.636s.047.25.111.397c.181.416.725.796.725.796s.669.57.502.625c-.156.068-5.08.067-5.241 0-.167-.057.502-.625.502-.625s.54-.38.724-.796c.066-.147.115-.397.115-.397l.934-4.637s.208-.102.35-.1c.139 0 .341.101.341.101z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,3 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#424242" fill-rule="evenodd" d="M4.95.143c-.241.034-.39.341-.305.622.03.098.045.116.299.37l.524.397.484.277s.414.11.283.372c-.027.058-.847 4.26-.847 4.26l-.066.275c-.037.19-.404.547-.404.547L3.246 8.94s-.421.433-.636.666c-.081.164-.017.43.098.542.15.151-.045.14 2.399.14 2.506 0 2.25.29 2.25.29s.194 4.946.281 5.22c.088.272.633.267.724 0 .078-.269.285-5.22.285-5.22s-.255-.282 2.245-.29c2.448.003 2.246.017 2.408-.141.107-.117.17-.397.09-.548-.212-.22-.628-.66-.628-.66s-1.13-1.119-1.687-1.676c-.35-.35-.359-.341-.398-.548a2.82 2.82 0 0 0-.067-.274s-.803-4.12-.847-4.26c-.121-.261.282-.372.282-.372l.485-.273s.16-.027.524-.401c.255-.256.277-.292.304-.37.086-.284.072-.585-.31-.626A35.293 35.293 0 0 0 8.006 0a36.01 36.01 0 0 0-3.058.143zm3.468 1.256l1.13 5.59s.057.301.134.48c.219.501.874.959.874.959s.807.686.606.753c-.189.082-6.126.081-6.32 0-.202-.068.605-.753.605-.753s.652-.46.874-.96c.078-.177.138-.479.138-.479l1.126-5.591s.25-.123.422-.121c.167 0 .411.122.411.122z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,3 +0,0 @@ | |||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="none" viewBox="0 0 16 16"> | |||
<path fill="#424242" fill-rule="evenodd" d="M10.579 1.28c-.195-.145-.517-.034-.655.226-.049.09-.05.114-.05.472l.09.652.146.538s.215.372-.064.463c-.06.022-3.61 2.413-3.61 2.413l-.242.148c-.16.11-.672.102-.672.102l-2.368.003s-.604.008-.92.02c-.174.06-.316.294-.315.454 0 .213-.13.067 1.597 1.795 1.773 1.772 1.386 1.796 1.386 1.796s-3.36 3.635-3.491 3.89c-.131.255.258.637.512.511.244-.134 3.891-3.488 3.891-3.488s.02-.38 1.794 1.382c1.728 1.733 1.576 1.6 1.801 1.602.159-.006.402-.16.453-.322.005-.307.02-.911.02-.911s-.006-1.59-.006-2.379c0-.494-.013-.495.106-.668.042-.062.112-.175.146-.242 0 0 2.346-3.481 2.414-3.612.099-.27.462-.063.462-.063l.536.15s.133.095.654.087c.362 0 .402-.01.477-.046.261-.14.465-.363.223-.663a35.31 35.31 0 0 0-2.052-2.248 36.038 36.038 0 0 0-2.263-2.061zm1.565 3.342L8.99 9.374s-.173.253-.244.434c-.2.51-.06 1.296-.06 1.296s.084 1.056-.105.96c-.192-.074-4.39-4.273-4.47-4.468-.093-.191.962-.105.962-.105s.785.137 1.296-.06c.181-.07.436-.242.436-.242l4.75-3.157s.264.09.384.213c.118.119.205.377.205.377z" clip-rule="evenodd"/> | |||
</svg> |
@ -1,184 +0,0 @@ | |||
'use strict'; | |||
import { commands, ConfigurationChangeEvent } from 'vscode'; | |||
import { CompareViewConfig, configuration, ViewFilesLayout } from '../configuration'; | |||
import { | |||
CommandContext, | |||
NamedRef, | |||
PinnedComparison, | |||
PinnedComparisons, | |||
setCommandContext, | |||
WorkspaceState, | |||
} from '../constants'; | |||
import { Container } from '../container'; | |||
import { CompareNode, CompareResultsNode, nodeSupportsConditionalDismissal, ViewNode } from './nodes'; | |||
import { ViewBase } from './viewBase'; | |||
export class CompareView extends ViewBase<CompareNode, CompareViewConfig> { | |||
protected readonly configKey = 'compare'; | |||
constructor() { | |||
super('gitlens.views.compare', 'Compare'); | |||
void setCommandContext(CommandContext.ViewsCompareKeepResults, this.keepResults); | |||
} | |||
getRoot() { | |||
return new CompareNode(this); | |||
} | |||
protected registerCommands() { | |||
void Container.viewCommands; | |||
commands.registerCommand(this.getQualifiedCommand('clear'), () => this.clear(), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('copy'), | |||
() => commands.executeCommand('gitlens.views.copy', this.selection), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToAuto'), | |||
() => this.setFilesLayout(ViewFilesLayout.Auto), | |||
this, | |||
); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToList'), | |||
() => this.setFilesLayout(ViewFilesLayout.List), | |||
this, | |||
); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToTree'), | |||
() => this.setFilesLayout(ViewFilesLayout.Tree), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('setKeepResultsToOn'), () => this.setKeepResults(true), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setKeepResultsToOff'), | |||
() => this.setKeepResults(false), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOn'), () => this.setShowAvatars(true), this); | |||
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOff'), () => this.setShowAvatars(false), this); | |||
commands.registerCommand(this.getQualifiedCommand('pinComparison'), this.pinComparison, this); | |||
commands.registerCommand(this.getQualifiedCommand('unpinComparison'), this.unpinComparison, this); | |||
commands.registerCommand(this.getQualifiedCommand('swapComparison'), this.swapComparison, this); | |||
commands.registerCommand(this.getQualifiedCommand('selectForCompare'), this.selectForCompare, this); | |||
commands.registerCommand(this.getQualifiedCommand('compareWithSelected'), this.compareWithSelected, this); | |||
} | |||
protected filterConfigurationChanged(e: ConfigurationChangeEvent) { | |||
const changed = super.filterConfigurationChanged(e); | |||
if ( | |||
!changed && | |||
!configuration.changed(e, 'defaultDateFormat') && | |||
!configuration.changed(e, 'defaultDateSource') && | |||
!configuration.changed(e, 'defaultDateStyle') && | |||
!configuration.changed(e, 'defaultGravatarsStyle') | |||
) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
get keepResults(): boolean { | |||
return Container.context.workspaceState.get<boolean>(WorkspaceState.ViewsCompareKeepResults, false); | |||
} | |||
clear() { | |||
this.root?.clear(); | |||
} | |||
dismissNode(node: ViewNode) { | |||
if (this.root == null) return; | |||
if (nodeSupportsConditionalDismissal(node) && node.canDismiss() === false) return; | |||
this.root.dismiss(node); | |||
} | |||
compare(repoPath: string, ref1: string | NamedRef, ref2: string | NamedRef) { | |||
return this.addResults( | |||
new CompareResultsNode( | |||
this, | |||
repoPath, | |||
typeof ref1 === 'string' ? { ref: ref1 } : ref1, | |||
typeof ref2 === 'string' ? { ref: ref2 } : ref2, | |||
), | |||
); | |||
} | |||
compareWithSelected(repoPath?: string, ref?: string | NamedRef) { | |||
const root = this.ensureRoot(); | |||
void root.compareWithSelected(repoPath, ref); | |||
} | |||
selectForCompare(repoPath?: string, ref?: string | NamedRef) { | |||
const root = this.ensureRoot(); | |||
void root.selectForCompare(repoPath, ref); | |||
} | |||
getPinnedComparisons() { | |||
const pinned = Container.context.workspaceState.get<PinnedComparisons>(WorkspaceState.PinnedComparisons); | |||
if (pinned == null) return []; | |||
return Object.values(pinned).map(p => new CompareResultsNode(this, p.path, p.ref1, p.ref2, true)); | |||
} | |||
async updatePinnedComparison(id: string, pin?: PinnedComparison) { | |||
let pinned = Container.context.workspaceState.get<PinnedComparisons>(WorkspaceState.PinnedComparisons); | |||
if (pinned == null) { | |||
pinned = Object.create(null) as PinnedComparisons; | |||
} | |||
if (pin !== undefined) { | |||
pinned[id] = { ...pin }; | |||
} else { | |||
const { [id]: _, ...rest } = pinned; | |||
pinned = rest; | |||
} | |||
await Container.context.workspaceState.update(WorkspaceState.PinnedComparisons, pinned); | |||
} | |||
private async addResults(results: ViewNode) { | |||
if (!this.visible) { | |||
await this.show(); | |||
} | |||
const root = this.ensureRoot(); | |||
root.addOrReplace(results, !this.keepResults); | |||
setImmediate(() => this.reveal(results, { expand: true, focus: true, select: true })); | |||
} | |||
private setFilesLayout(layout: ViewFilesLayout) { | |||
return configuration.updateEffective('views', this.configKey, 'files', 'layout', layout); | |||
} | |||
private setKeepResults(enabled: boolean) { | |||
void Container.context.workspaceState.update(WorkspaceState.ViewsCompareKeepResults, enabled); | |||
void setCommandContext(CommandContext.ViewsCompareKeepResults, enabled); | |||
} | |||
private setShowAvatars(enabled: boolean) { | |||
return configuration.updateEffective('views', this.configKey, 'avatars', enabled); | |||
} | |||
private pinComparison(node: ViewNode) { | |||
if (!(node instanceof CompareResultsNode)) return undefined; | |||
return node.pin(); | |||
} | |||
private swapComparison(node: ViewNode) { | |||
if (!(node instanceof CompareResultsNode)) return undefined; | |||
return node.swap(); | |||
} | |||
private unpinComparison(node: ViewNode) { | |||
if (!(node instanceof CompareResultsNode)) return undefined; | |||
return node.unpin(); | |||
} | |||
} |
@ -1,231 +0,0 @@ | |||
'use strict'; | |||
import { TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { getRepoPathOrPrompt } from '../../commands'; | |||
import { BranchSorting, TagSorting } from '../../configuration'; | |||
import { CommandContext, NamedRef, setCommandContext } from '../../constants'; | |||
import { GitRevision } from '../../git/git'; | |||
import { ReferencePicker, ReferencesQuickPickIncludes } from '../../quickpicks'; | |||
import { debug, gate, Iterables, log, Promises } from '../../system'; | |||
import { CompareView } from '../compareView'; | |||
import { MessageNode } from './common'; | |||
import { ComparePickerNode } from './comparePickerNode'; | |||
import { ContextValues, unknownGitUri, ViewNode } from './viewNode'; | |||
interface RepoRef { | |||
label: string; | |||
repoPath: string; | |||
ref: string | NamedRef; | |||
} | |||
export class CompareNode extends ViewNode<CompareView> { | |||
private _children: (ViewNode | MessageNode)[] = []; | |||
private _comparePickerNode: ComparePickerNode | undefined; | |||
constructor(view: CompareView) { | |||
super(unknownGitUri, view); | |||
} | |||
private _selectedRef: RepoRef | undefined; | |||
get selectedRef(): RepoRef | undefined { | |||
return this._selectedRef; | |||
} | |||
getChildren(): ViewNode[] { | |||
if (this._children.length === 0) { | |||
// Not really sure why I can't reuse this node -- but if I do the Tree errors out with an id already exists error | |||
this._comparePickerNode = new ComparePickerNode(this.view, this); | |||
this._children = [this._comparePickerNode]; | |||
const pinned = this.view.getPinnedComparisons(); | |||
if (pinned.length !== 0) { | |||
this._children.push(...pinned); | |||
} | |||
} else if (this._comparePickerNode === undefined || !this._children.includes(this._comparePickerNode)) { | |||
// Not really sure why I can't reuse this node -- but if I do the Tree errors out with an id already exists error | |||
this._comparePickerNode = new ComparePickerNode(this.view, this); | |||
this._children.splice(0, 0, this._comparePickerNode); | |||
if (this._selectedRef !== undefined) { | |||
const node = this._comparePickerNode; | |||
setImmediate(() => this.view.reveal(node, { focus: false, select: true })); | |||
} | |||
} | |||
return this._children; | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem('Compare', TreeItemCollapsibleState.Expanded); | |||
item.contextValue = ContextValues.Compare; | |||
return item; | |||
} | |||
addOrReplace(results: ViewNode, replace: boolean) { | |||
if (this._children.includes(results)) return; | |||
if (this._children.length !== 0 && replace) { | |||
this._children.length = 0; | |||
this._children.push(results); | |||
// Re-add the pinned comparisons | |||
const pinned = this.view.getPinnedComparisons(); | |||
if (pinned.length !== 0) { | |||
this._children.push(...pinned); | |||
} | |||
} else { | |||
if (this._comparePickerNode !== undefined) { | |||
const index = this._children.indexOf(this._comparePickerNode); | |||
if (index !== -1) { | |||
this._children.splice(index, 1); | |||
} | |||
} | |||
this._children.splice(0, 0, results); | |||
} | |||
this.view.triggerNodeChange(); | |||
} | |||
@log() | |||
clear() { | |||
this._selectedRef = undefined; | |||
void setCommandContext(CommandContext.ViewsCanCompare, false); | |||
this._children.length = 0; | |||
this.view.triggerNodeChange(); | |||
} | |||
@log({ | |||
args: { 0: (n: ViewNode) => n.toString() }, | |||
}) | |||
dismiss(node: ViewNode) { | |||
this._selectedRef = undefined; | |||
void setCommandContext(CommandContext.ViewsCanCompare, false); | |||
if (this._children.length !== 0) { | |||
const index = this._children.indexOf(node); | |||
if (index === -1) return; | |||
this._children.splice(index, 1); | |||
} | |||
this.view.triggerNodeChange(); | |||
} | |||
@gate() | |||
@debug() | |||
async refresh() { | |||
if (this._children.length === 0) return; | |||
const promises: Promise<any>[] = [ | |||
...Iterables.filterMap(this._children, c => { | |||
const result = c.refresh === undefined ? false : c.refresh(); | |||
return Promises.is<boolean | void>(result) ? result : undefined; | |||
}), | |||
]; | |||
await Promise.all(promises); | |||
} | |||
async compareWithSelected(repoPath?: string, ref?: string | NamedRef) { | |||
if (this._selectedRef === undefined) return; | |||
if (repoPath === undefined) { | |||
repoPath = this._selectedRef.repoPath; | |||
} else if (repoPath !== this._selectedRef.repoPath) { | |||
// If we don't have a matching repoPath, then start over | |||
void this.selectForCompare(repoPath, ref); | |||
return; | |||
} | |||
if (ref === undefined) { | |||
const pick = await ReferencePicker.show( | |||
repoPath, | |||
`Compare ${this.getRefName(this._selectedRef.ref)} with`, | |||
'Choose a reference to compare with', | |||
{ | |||
allowEnteringRefs: true, | |||
picked: | |||
typeof this._selectedRef.ref === 'string' ? this._selectedRef.ref : this._selectedRef.ref.ref, | |||
// checkmarks: true, | |||
include: | |||
ReferencesQuickPickIncludes.BranchesAndTags | | |||
ReferencesQuickPickIncludes.HEAD | | |||
ReferencesQuickPickIncludes.WorkingTree, | |||
sort: { | |||
branches: { current: true, orderBy: BranchSorting.DateDesc }, | |||
tags: { orderBy: TagSorting.DateDesc }, | |||
}, | |||
}, | |||
); | |||
if (pick === undefined) { | |||
await this.view.show(); | |||
await this.view.reveal(this._comparePickerNode!, { focus: true, select: true }); | |||
return; | |||
} | |||
ref = pick.ref; | |||
} | |||
const ref1 = this._selectedRef; | |||
this._selectedRef = undefined; | |||
void setCommandContext(CommandContext.ViewsCanCompare, false); | |||
void (await this.view.compare(repoPath, ref1.ref, ref)); | |||
} | |||
async selectForCompare(repoPath?: string, ref?: string | NamedRef) { | |||
if (repoPath === undefined) { | |||
repoPath = await getRepoPathOrPrompt('Compare'); | |||
} | |||
if (repoPath === undefined) { | |||
await this.view.show(); | |||
await this.view.reveal(this._comparePickerNode!, { focus: true, select: true }); | |||
return; | |||
} | |||
let autoCompare = false; | |||
if (ref === undefined) { | |||
const pick = await ReferencePicker.show(repoPath, 'Compare', 'Choose a reference to compare', { | |||
allowEnteringRefs: true, | |||
// checkmarks: false, | |||
include: | |||
ReferencesQuickPickIncludes.BranchesAndTags | | |||
ReferencesQuickPickIncludes.HEAD | | |||
ReferencesQuickPickIncludes.WorkingTree, | |||
sort: { | |||
branches: { current: true, orderBy: BranchSorting.DateDesc }, | |||
tags: { orderBy: TagSorting.DateDesc }, | |||
}, | |||
}); | |||
if (pick == null) { | |||
await this.view.show(); | |||
await this.view.reveal(this._comparePickerNode!, { focus: true, select: true }); | |||
return; | |||
} | |||
ref = pick.ref; | |||
autoCompare = true; | |||
} | |||
this._selectedRef = { label: this.getRefName(ref), repoPath: repoPath, ref: ref }; | |||
void setCommandContext(CommandContext.ViewsCanCompare, true); | |||
void (await this.triggerChange()); | |||
await this.view.reveal(this._comparePickerNode!, { focus: true, select: true }); | |||
if (autoCompare) { | |||
void (await this.compareWithSelected()); | |||
} | |||
} | |||
private getRefName(ref: string | NamedRef) { | |||
return typeof ref === 'string' | |||
? GitRevision.shorten(ref, { strings: { working: 'Working Tree' } })! | |||
: ref.label ?? GitRevision.shorten(ref.ref)!; | |||
} | |||
} |
@ -1,147 +0,0 @@ | |||
'use strict'; | |||
import { TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { SearchCommitsCommandArgs } from '../../commands'; | |||
import { GlyphChars } from '../../constants'; | |||
import { debug, gate, Iterables, log, Promises } from '../../system'; | |||
import { View } from '../viewBase'; | |||
import { CommandMessageNode, MessageNode } from './common'; | |||
import { ContextValues, unknownGitUri, ViewNode } from './viewNode'; | |||
import { SearchOperators } from '../../git/git'; | |||
export class SearchNode extends ViewNode { | |||
private _children: (ViewNode | MessageNode)[] = []; | |||
constructor(view: View) { | |||
super(unknownGitUri, view); | |||
} | |||
getChildren(): ViewNode[] { | |||
if (this._children.length === 0) { | |||
const command = { | |||
title: ' ', | |||
command: 'gitlens.showCommitSearch', | |||
}; | |||
const getCommandArgs = (search: SearchOperators): SearchCommitsCommandArgs => { | |||
return { | |||
search: { pattern: search }, | |||
prefillOnly: true, | |||
}; | |||
}; | |||
return [ | |||
new CommandMessageNode( | |||
this.view, | |||
this, | |||
{ | |||
...command, | |||
arguments: [this, getCommandArgs('message:')], | |||
}, | |||
'Search by Message', | |||
`pattern or message: pattern or =: pattern ${GlyphChars.Dash} use quotes to search for phrases`, | |||
`Click to search for commits with matching messages ${GlyphChars.Dash} use quotes to search for phrases`, | |||
), | |||
new CommandMessageNode( | |||
this.view, | |||
this, | |||
{ | |||
...command, | |||
arguments: [this, getCommandArgs('author:')], | |||
}, | |||
`${GlyphChars.Space.repeat(4)} or, Author`, | |||
'author: pattern or @: pattern', | |||
'Click to search for commits with matching authors', | |||
), | |||
new CommandMessageNode( | |||
this.view, | |||
this, | |||
{ | |||
...command, | |||
arguments: [this, getCommandArgs('commit:')], | |||
}, | |||
`${GlyphChars.Space.repeat(4)} or, Commit ID`, | |||
'commit: sha or #: sha', | |||
'Click to search for commits with matching commit ids', | |||
), | |||
new CommandMessageNode( | |||
this.view, | |||
this, | |||
{ | |||
...command, | |||
arguments: [this, getCommandArgs('file:')], | |||
}, | |||
`${GlyphChars.Space.repeat(4)} or, Files`, | |||
'file: glob or ?: glob', | |||
'Click to search for commits with matching files', | |||
), | |||
new CommandMessageNode( | |||
this.view, | |||
this, | |||
{ | |||
...command, | |||
arguments: [this, getCommandArgs('change:')], | |||
}, | |||
`${GlyphChars.Space.repeat(4)} or, Changes`, | |||
'change: pattern or ~: pattern', | |||
'Click to search for commits with matching changes', | |||
), | |||
]; | |||
} | |||
return this._children; | |||
} | |||
getTreeItem(): TreeItem { | |||
const item = new TreeItem('Search', TreeItemCollapsibleState.Expanded); | |||
item.contextValue = ContextValues.Search; | |||
return item; | |||
} | |||
addOrReplace(results: ViewNode, replace: boolean) { | |||
if (this._children.includes(results)) return; | |||
if (this._children.length !== 0 && replace) { | |||
this._children.length = 0; | |||
this._children.push(results); | |||
} else { | |||
this._children.splice(0, 0, results); | |||
} | |||
this.view.triggerNodeChange(); | |||
} | |||
@log() | |||
clear() { | |||
if (this._children.length === 0) return; | |||
this._children.length = 0; | |||
this.view.triggerNodeChange(); | |||
} | |||
@log({ | |||
args: { 0: (n: ViewNode) => n.toString() }, | |||
}) | |||
dismiss(node: ViewNode) { | |||
if (this._children.length === 0) return; | |||
const index = this._children.findIndex(n => n === node); | |||
if (index === -1) return; | |||
this._children.splice(index, 1); | |||
this.view.triggerNodeChange(); | |||
} | |||
@gate() | |||
@debug() | |||
async refresh() { | |||
if (this._children.length === 0) return; | |||
const promises: Promise<any>[] = [ | |||
...Iterables.filterMap(this._children, c => { | |||
const result = c.refresh === undefined ? false : c.refresh(); | |||
return Promises.is<boolean | void>(result) ? result : undefined; | |||
}), | |||
]; | |||
await Promise.all(promises); | |||
} | |||
} |
@ -1,64 +0,0 @@ | |||
'use strict'; | |||
import { TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { Commands, SearchCommitsCommandArgs } from '../../commands'; | |||
import { ViewsWithFiles } from '../viewBase'; | |||
import { CommitsQueryResults, ResultsCommitsNode } from './resultsCommitsNode'; | |||
import { ContextValues, ViewNode } from './viewNode'; | |||
import { RepositoryNode } from './repositoryNode'; | |||
import { SearchPattern } from '../../git/git'; | |||
let instanceId = 0; | |||
export class SearchResultsCommitsNode extends ResultsCommitsNode { | |||
static key = ':search-results'; | |||
static getId(repoPath: string, search: SearchPattern | undefined, instanceId: number): string { | |||
return `${RepositoryNode.getId(repoPath)}${this.key}(${ | |||
search === undefined ? '?' : SearchPattern.toKey(search) | |||
}):${instanceId}`; | |||
} | |||
private _instanceId: number; | |||
constructor( | |||
view: ViewsWithFiles, | |||
parent: ViewNode, | |||
repoPath: string, | |||
public readonly search: SearchPattern, | |||
label: string, | |||
commitsQuery: (limit: number | undefined) => Promise<CommitsQueryResults>, | |||
) { | |||
super(view, parent, repoPath, label, commitsQuery, { | |||
expand: true, | |||
includeRepoName: true, | |||
}); | |||
this._instanceId = instanceId++; | |||
} | |||
get id(): string { | |||
return SearchResultsCommitsNode.getId(this.repoPath, this.search, this._instanceId); | |||
} | |||
get type(): ContextValues { | |||
return ContextValues.SearchResults; | |||
} | |||
async getTreeItem(): Promise<TreeItem> { | |||
const item = await super.getTreeItem(); | |||
if (item.collapsibleState === TreeItemCollapsibleState.None) { | |||
const args: SearchCommitsCommandArgs = { | |||
search: this.search, | |||
prefillOnly: true, | |||
showResultsInSideBar: true, | |||
}; | |||
item.command = { | |||
title: 'Search Commits', | |||
command: Commands.SearchCommitsInView, | |||
arguments: [args], | |||
}; | |||
} | |||
return item; | |||
} | |||
} |
@ -0,0 +1,297 @@ | |||
'use strict'; | |||
import { ThemeIcon, TreeItem } from 'vscode'; | |||
import { executeGitCommand } from '../../commands'; | |||
import { Container } from '../../container'; | |||
import { GitLog, SearchPattern } from '../../git/git'; | |||
import { GitUri } from '../../git/gitUri'; | |||
import { RepositoryNode } from './repositoryNode'; | |||
import { CommitsQueryResults, ResultsCommitsNode } from './resultsCommitsNode'; | |||
import { SearchAndCompareView } from '../searchAndCompareView'; | |||
import { debug, gate, log, Strings } from '../../system'; | |||
import { ContextValues, PageableViewNode, ViewNode } from './viewNode'; | |||
let instanceId = 0; | |||
interface SearchQueryResults { | |||
readonly label: string; | |||
readonly log: GitLog | undefined; | |||
readonly hasMore: boolean; | |||
more?(limit: number | undefined): Promise<void>; | |||
} | |||
export class SearchResultsNode extends ViewNode<SearchAndCompareView> implements PageableViewNode { | |||
static key = ':search-results'; | |||
static getId(repoPath: string, search: SearchPattern | undefined, instanceId: number): string { | |||
return `${RepositoryNode.getId(repoPath)}${this.key}(${ | |||
search == null ? '?' : SearchPattern.toKey(search) | |||
}):${instanceId}`; | |||
} | |||
static getPinnableId(repoPath: string, search: SearchPattern) { | |||
return Strings.sha1(`${repoPath}|${SearchPattern.toKey(search)}`); | |||
} | |||
static is(node: any): node is SearchResultsNode { | |||
return node instanceof SearchResultsNode; | |||
} | |||
private _instanceId: number; | |||
constructor( | |||
view: SearchAndCompareView, | |||
parent: ViewNode, | |||
public readonly repoPath: string, | |||
search: SearchPattern, | |||
private _labels: { | |||
label: string; | |||
queryLabel: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
resultsType?: { singular: string; plural: string }; | |||
}, | |||
private _searchQueryOrLog?: | |||
| ((limit: number | undefined) => Promise<CommitsQueryResults>) | |||
| Promise<GitLog | undefined> | |||
| GitLog | |||
| undefined, | |||
private _pinned: number = 0, | |||
) { | |||
super(GitUri.fromRepoPath(repoPath), view, parent); | |||
this._search = search; | |||
this._instanceId = instanceId++; | |||
this._order = Date.now(); | |||
} | |||
get id(): string { | |||
return SearchResultsNode.getId(this.repoPath, this.search, this._instanceId); | |||
} | |||
get canDismiss(): boolean { | |||
return !this.pinned; | |||
} | |||
private readonly _order: number = Date.now(); | |||
get order(): number { | |||
return this._pinned || this._order; | |||
} | |||
get pinned(): boolean { | |||
return this._pinned !== 0; | |||
} | |||
private _search: SearchPattern; | |||
get search(): SearchPattern { | |||
return this._search; | |||
} | |||
private _resultsNode: ResultsCommitsNode | undefined; | |||
private ensureResults() { | |||
if (this._resultsNode == null) { | |||
let deferred; | |||
if (this._searchQueryOrLog == null) { | |||
deferred = true; | |||
this._searchQueryOrLog = this.getSearchQuery({ | |||
label: this._labels.queryLabel, | |||
}); | |||
} else if (typeof this._searchQueryOrLog !== 'function') { | |||
this._searchQueryOrLog = this.getSearchQuery( | |||
{ | |||
label: this._labels.queryLabel, | |||
}, | |||
this._searchQueryOrLog, | |||
); | |||
} | |||
this._resultsNode = new ResultsCommitsNode( | |||
this.view, | |||
this, | |||
this.repoPath, | |||
this._labels.label, | |||
{ | |||
query: this._searchQueryOrLog, | |||
deferred: deferred, | |||
}, | |||
{ | |||
expand: !this.pinned, | |||
}, | |||
true, | |||
); | |||
} | |||
return this._resultsNode; | |||
} | |||
async getChildren(): Promise<ViewNode[]> { | |||
return this.ensureResults().getChildren(); | |||
} | |||
async getTreeItem(): Promise<TreeItem> { | |||
const item = await this.ensureResults().getTreeItem(); | |||
item.contextValue = `${ContextValues.SearchResults}${this._pinned ? '+pinned' : ''}`; | |||
if ((await Container.git.getRepositoryCount()) > 1) { | |||
const repo = await Container.git.getRepository(this.repoPath); | |||
item.description = repo?.formattedName ?? this.repoPath; | |||
} | |||
if (this._pinned) { | |||
item.iconPath = new ThemeIcon('pinned'); | |||
} | |||
// if (item.collapsibleState === TreeItemCollapsibleState.None) { | |||
// const args: SearchCommitsCommandArgs = { | |||
// search: this.search, | |||
// prefillOnly: true, | |||
// showResultsInSideBar: true, | |||
// }; | |||
// item.command = { | |||
// title: 'Search Commits', | |||
// command: Commands.SearchCommitsInView, | |||
// arguments: [args], | |||
// }; | |||
// } | |||
return item; | |||
} | |||
get hasMore() { | |||
return this.ensureResults().hasMore; | |||
} | |||
async loadMore(limit?: number) { | |||
return this.ensureResults().loadMore(limit); | |||
} | |||
async edit(search?: { | |||
pattern: SearchPattern; | |||
labels: { | |||
label: string; | |||
queryLabel: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
log: Promise<GitLog | undefined> | GitLog | undefined; | |||
}) { | |||
if (search == null) { | |||
void (await executeGitCommand({ | |||
command: 'search', | |||
prefillOnly: true, | |||
state: { | |||
repo: this.repoPath, | |||
...this.search, | |||
showResultsInSideBar: this, | |||
}, | |||
})); | |||
return; | |||
} | |||
this._search = search.pattern; | |||
this._labels = search.labels; | |||
this._searchQueryOrLog = search.log; | |||
this._resultsNode = undefined; | |||
void this.triggerChange(false); | |||
} | |||
@gate() | |||
@debug() | |||
refresh(reset: boolean = false) { | |||
this._resultsNode?.refresh(reset); | |||
} | |||
@log() | |||
async pin() { | |||
if (this.pinned) return; | |||
this._pinned = Date.now(); | |||
await this.view.updatePinned(this.getPinnableId(), { | |||
type: 'search', | |||
timestamp: this._pinned, | |||
path: this.repoPath, | |||
labels: this._labels, | |||
search: this.search, | |||
}); | |||
setImmediate(() => this.view.reveal(this, { focus: true, select: true })); | |||
} | |||
@log() | |||
async unpin() { | |||
if (!this.pinned) return; | |||
this._pinned = 0; | |||
await this.view.updatePinned(this.getPinnableId()); | |||
setImmediate(() => this.view.reveal(this, { focus: true, select: true })); | |||
} | |||
private getPinnableId() { | |||
return SearchResultsNode.getPinnableId(this.repoPath, this.search); | |||
} | |||
private getSearchLabel( | |||
label: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}, | |||
log: GitLog | undefined, | |||
): string { | |||
if (typeof label === 'string') return label; | |||
const count = log?.count ?? 0; | |||
const resultsType = | |||
label.resultsType === undefined ? { singular: 'result', plural: 'results' } : label.resultsType; | |||
return `${Strings.pluralize(resultsType.singular, count, { | |||
number: log?.hasMore ?? false ? `${count}+` : undefined, | |||
plural: resultsType.plural, | |||
zero: 'No', | |||
})} ${label.label}`; | |||
} | |||
private getSearchQuery( | |||
options: { | |||
label: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
}, | |||
log?: Promise<GitLog | undefined> | GitLog, | |||
): (limit: number | undefined) => Promise<SearchQueryResults> { | |||
let useCacheOnce = true; | |||
return async (limit: number | undefined) => { | |||
log = await (log ?? Container.git.getLogForSearch(this.repoPath, this.search)); | |||
if (!useCacheOnce && log != null && log.query != null) { | |||
log = await log.query(limit); | |||
} | |||
useCacheOnce = false; | |||
const results: Mutable<SearchQueryResults> = { | |||
label: this.getSearchLabel(options.label, log), | |||
log: log, | |||
hasMore: log?.hasMore ?? false, | |||
}; | |||
if (results.hasMore) { | |||
results.more = async (limit: number | undefined) => { | |||
results.log = (await results.log?.more?.(limit)) ?? results.log; | |||
results.label = this.getSearchLabel(options.label, results.log); | |||
results.hasMore = results.log?.hasMore ?? true; | |||
}; | |||
} | |||
return results; | |||
}; | |||
} | |||
} |
@ -0,0 +1,502 @@ | |||
'use strict'; | |||
import { commands, ConfigurationChangeEvent, TreeItem, TreeItemCollapsibleState } from 'vscode'; | |||
import { | |||
BranchSorting, | |||
configuration, | |||
SearchAndCompareViewConfig, | |||
TagSorting, | |||
ViewFilesLayout, | |||
} from '../configuration'; | |||
import { CommandContext, NamedRef, PinnedItem, PinnedItems, setCommandContext, WorkspaceState } from '../constants'; | |||
import { Container } from '../container'; | |||
import { GitLog, GitRevision, SearchPattern } from '../git/git'; | |||
import { CompareResultsNode, ContextValues, SearchResultsNode, unknownGitUri, ViewNode } from './nodes'; | |||
import { debug, gate, Iterables, log, Promises } from '../system'; | |||
import { ViewBase } from './viewBase'; | |||
import { ComparePickerNode } from './nodes/comparePickerNode'; | |||
import { ReferencePicker, ReferencesQuickPickIncludes } from '../quickpicks'; | |||
import { getRepoPathOrPrompt } from '../commands'; | |||
interface DeprecatedPinnedComparison { | |||
path: string; | |||
ref1: NamedRef; | |||
ref2: NamedRef; | |||
notation?: '..' | '...'; | |||
} | |||
interface DeprecatedPinnedComparisons { | |||
[id: string]: DeprecatedPinnedComparison; | |||
} | |||
export class SearchAndCompareViewNode extends ViewNode<SearchAndCompareView> { | |||
protected splatted = true; | |||
private comparePicker: ComparePickerNode | undefined; | |||
constructor(view: SearchAndCompareView) { | |||
super(unknownGitUri, view); | |||
} | |||
private _children: (ComparePickerNode | CompareResultsNode | SearchResultsNode)[] | undefined; | |||
private get children(): (ComparePickerNode | CompareResultsNode | SearchResultsNode)[] { | |||
if (this._children == null) { | |||
this._children = []; | |||
// Get pinned searches & comparisons | |||
const pinned = this.view.getPinned(); | |||
if (pinned.length !== 0) { | |||
this._children.push(...pinned); | |||
} | |||
} | |||
return this._children; | |||
} | |||
getChildren(): ViewNode[] { | |||
return this.children.sort((a, b) => (a.pinned ? -1 : 1) - (b.pinned ? -1 : 1) || b.order - a.order); | |||
} | |||
getTreeItem(): TreeItem { | |||
this.splatted = false; | |||
const item = new TreeItem('SearchAndCompare', TreeItemCollapsibleState.Expanded); | |||
item.contextValue = ContextValues.SearchAndCompare; | |||
return item; | |||
} | |||
addOrReplace(results: CompareResultsNode | SearchResultsNode, replace: boolean) { | |||
if (this.children.includes(results)) return; | |||
if (replace) { | |||
this.clear(); | |||
} | |||
this.children.push(results); | |||
this.view.triggerNodeChange(); | |||
} | |||
@log() | |||
clear(silent: boolean = false) { | |||
if (this.children.length === 0) return; | |||
this.removeComparePicker(true); | |||
const index = this._children!.findIndex(c => !c.pinned); | |||
if (index !== -1) { | |||
this._children!.splice(index, this._children!.length); | |||
} | |||
if (!silent) { | |||
this.view.triggerNodeChange(); | |||
} | |||
} | |||
@log({ | |||
args: { 0: (n: ViewNode) => n.toString() }, | |||
}) | |||
dismiss(node: ComparePickerNode | CompareResultsNode | SearchResultsNode) { | |||
if (node === this.comparePicker) { | |||
this.removeComparePicker(); | |||
return; | |||
} | |||
if (this.children.length === 0) return; | |||
const index = this.children.indexOf(node); | |||
if (index === -1) return; | |||
this.children.splice(index, 1); | |||
this.view.triggerNodeChange(); | |||
} | |||
@gate() | |||
@debug() | |||
async refresh() { | |||
if (this.children.length === 0) return; | |||
const promises: Promise<any>[] = [ | |||
...Iterables.filterMap(this.children, c => { | |||
const result = c.refresh === undefined ? false : c.refresh(); | |||
return Promises.is<boolean | void>(result) ? result : undefined; | |||
}), | |||
]; | |||
await Promise.all(promises); | |||
} | |||
async compareWithSelected(repoPath?: string, ref?: string | NamedRef) { | |||
const selectedRef = this.comparePicker?.selectedRef; | |||
if (selectedRef == null) return; | |||
if (repoPath == null) { | |||
repoPath = selectedRef.repoPath; | |||
} else if (repoPath !== selectedRef.repoPath) { | |||
// If we don't have a matching repoPath, then start over | |||
void this.selectForCompare(repoPath, ref); | |||
return; | |||
} | |||
if (ref == null) { | |||
const pick = await ReferencePicker.show( | |||
repoPath, | |||
`Compare ${this.getRefName(selectedRef.ref)} with`, | |||
'Choose a reference to compare with', | |||
{ | |||
allowEnteringRefs: true, | |||
picked: typeof selectedRef.ref === 'string' ? selectedRef.ref : selectedRef.ref.ref, | |||
// checkmarks: true, | |||
include: | |||
ReferencesQuickPickIncludes.BranchesAndTags | | |||
ReferencesQuickPickIncludes.HEAD | | |||
ReferencesQuickPickIncludes.WorkingTree, | |||
sort: { | |||
branches: { current: true, orderBy: BranchSorting.DateDesc }, | |||
tags: { orderBy: TagSorting.DateDesc }, | |||
}, | |||
}, | |||
); | |||
if (pick == null) { | |||
if (this.comparePicker != null) { | |||
await this.view.show(); | |||
await this.view.reveal(this.comparePicker, { focus: true, select: true }); | |||
} | |||
return; | |||
} | |||
ref = pick.ref; | |||
} | |||
this.removeComparePicker(); | |||
void (await this.view.compare(repoPath, selectedRef.ref, ref)); | |||
} | |||
async selectForCompare(repoPath?: string, ref?: string | NamedRef) { | |||
if (repoPath == null) { | |||
repoPath = await getRepoPathOrPrompt('Compare'); | |||
} | |||
if (repoPath == null) return; | |||
this.removeComparePicker(true); | |||
let autoCompare = false; | |||
if (ref == null) { | |||
const pick = await ReferencePicker.show(repoPath, 'Compare', 'Choose a reference to compare', { | |||
allowEnteringRefs: true, | |||
// checkmarks: false, | |||
include: | |||
ReferencesQuickPickIncludes.BranchesAndTags | | |||
ReferencesQuickPickIncludes.HEAD | | |||
ReferencesQuickPickIncludes.WorkingTree, | |||
sort: { | |||
branches: { current: true, orderBy: BranchSorting.DateDesc }, | |||
tags: { orderBy: TagSorting.DateDesc }, | |||
}, | |||
}); | |||
if (pick == null) { | |||
await this.triggerChange(); | |||
return; | |||
} | |||
ref = pick.ref; | |||
autoCompare = true; | |||
} | |||
this.comparePicker = new ComparePickerNode(this.view, this, { | |||
label: this.getRefName(ref), | |||
repoPath: repoPath, | |||
ref: ref, | |||
}); | |||
this.children.splice(0, 0, this.comparePicker); | |||
void setCommandContext(CommandContext.ViewsCanCompare, true); | |||
await this.triggerChange(); | |||
await this.view.reveal(this.comparePicker, { focus: false, select: true }); | |||
if (autoCompare) { | |||
await this.compareWithSelected(); | |||
} | |||
} | |||
private getRefName(ref: string | NamedRef) { | |||
return typeof ref === 'string' | |||
? GitRevision.shorten(ref, { strings: { working: 'Working Tree' } })! | |||
: ref.label ?? GitRevision.shorten(ref.ref)!; | |||
} | |||
private removeComparePicker(silent: boolean = false) { | |||
void setCommandContext(CommandContext.ViewsCanCompare, false); | |||
if (this.comparePicker != null) { | |||
const index = this.children.indexOf(this.comparePicker); | |||
if (index !== -1) { | |||
this.children.splice(index, 1); | |||
if (!silent) { | |||
void this.triggerChange(); | |||
} | |||
} | |||
this.comparePicker = undefined; | |||
} | |||
} | |||
} | |||
export class SearchAndCompareView extends ViewBase<SearchAndCompareViewNode, SearchAndCompareViewConfig> { | |||
protected readonly configKey = 'searchAndCompare'; | |||
constructor() { | |||
super('gitlens.views.searchAndCompare', 'Search & Compare'); | |||
void setCommandContext(CommandContext.ViewsSearchAndCompareKeepResults, this.keepResults); | |||
} | |||
getRoot() { | |||
return new SearchAndCompareViewNode(this); | |||
} | |||
protected registerCommands() { | |||
void Container.viewCommands; | |||
commands.registerCommand(this.getQualifiedCommand('clear'), () => this.clear(), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('copy'), | |||
() => commands.executeCommand('gitlens.views.copy', this.selection), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToAuto'), | |||
() => this.setFilesLayout(ViewFilesLayout.Auto), | |||
this, | |||
); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToList'), | |||
() => this.setFilesLayout(ViewFilesLayout.List), | |||
this, | |||
); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToTree'), | |||
() => this.setFilesLayout(ViewFilesLayout.Tree), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('setKeepResultsToOn'), () => this.setKeepResults(true), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setKeepResultsToOff'), | |||
() => this.setKeepResults(false), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOn'), () => this.setShowAvatars(true), this); | |||
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOff'), () => this.setShowAvatars(false), this); | |||
commands.registerCommand(this.getQualifiedCommand('pin'), this.pin, this); | |||
commands.registerCommand(this.getQualifiedCommand('unpin'), this.unpin, this); | |||
commands.registerCommand(this.getQualifiedCommand('edit'), this.edit, this); | |||
commands.registerCommand(this.getQualifiedCommand('swapComparison'), this.swapComparison, this); | |||
commands.registerCommand(this.getQualifiedCommand('selectForCompare'), this.selectForCompare, this); | |||
commands.registerCommand(this.getQualifiedCommand('compareWithSelected'), this.compareWithSelected, this); | |||
} | |||
protected filterConfigurationChanged(e: ConfigurationChangeEvent) { | |||
const changed = super.filterConfigurationChanged(e); | |||
if ( | |||
!changed && | |||
!configuration.changed(e, 'defaultDateFormat') && | |||
!configuration.changed(e, 'defaultDateSource') && | |||
!configuration.changed(e, 'defaultDateStyle') && | |||
!configuration.changed(e, 'defaultGravatarsStyle') | |||
) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
get keepResults(): boolean { | |||
return Container.context.workspaceState.get<boolean>(WorkspaceState.ViewsSearchAndCompareKeepResults, true); | |||
} | |||
clear() { | |||
this.root?.clear(); | |||
} | |||
dismissNode(node: ViewNode) { | |||
if ( | |||
this.root == null || | |||
(!(node instanceof ComparePickerNode) && | |||
!(node instanceof CompareResultsNode) && | |||
!(node instanceof SearchResultsNode)) || | |||
!node.canDismiss | |||
) { | |||
return; | |||
} | |||
this.root.dismiss(node); | |||
} | |||
compare(repoPath: string, ref1: string | NamedRef, ref2: string | NamedRef) { | |||
return this.addResults( | |||
new CompareResultsNode( | |||
this, | |||
this.ensureRoot(), | |||
repoPath, | |||
typeof ref1 === 'string' ? { ref: ref1 } : ref1, | |||
typeof ref2 === 'string' ? { ref: ref2 } : ref2, | |||
), | |||
); | |||
} | |||
compareWithSelected(repoPath?: string, ref?: string | NamedRef) { | |||
void this.ensureRoot().compareWithSelected(repoPath, ref); | |||
} | |||
selectForCompare(repoPath?: string, ref?: string | NamedRef) { | |||
void this.ensureRoot().selectForCompare(repoPath, ref); | |||
} | |||
async search( | |||
repoPath: string, | |||
search: SearchPattern, | |||
{ | |||
label, | |||
reveal, | |||
}: { | |||
label: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
reveal?: { | |||
select?: boolean; | |||
focus?: boolean; | |||
expand?: boolean | number; | |||
}; | |||
}, | |||
results?: Promise<GitLog | undefined> | GitLog, | |||
updateNode?: SearchResultsNode, | |||
) { | |||
if (!this.visible) { | |||
await this.show(); | |||
} | |||
const labels = { label: `Results ${typeof label === 'string' ? label : label.label}`, queryLabel: label }; | |||
if (updateNode != null) { | |||
await updateNode.edit({ pattern: search, labels: labels, log: results }); | |||
return; | |||
} | |||
await this.addResults(new SearchResultsNode(this, this.root!, repoPath, search, labels, results), reveal); | |||
} | |||
getPinned() { | |||
let savedPins = Container.context.workspaceState.get<PinnedItems>( | |||
WorkspaceState.ViewsSearchAndComparePinnedItems, | |||
); | |||
if (savedPins == null) { | |||
// Migrate any deprecated pinned items | |||
const deprecatedPins = Container.context.workspaceState.get<DeprecatedPinnedComparisons>( | |||
WorkspaceState.DeprecatedPinnedComparisons, | |||
); | |||
if (deprecatedPins == null) return []; | |||
savedPins = Object.create(null) as PinnedItems; | |||
for (const p of Object.values(deprecatedPins)) { | |||
savedPins[CompareResultsNode.getPinnableId(p.path, p.ref1.ref, p.ref2.ref)] = { | |||
type: 'comparison', | |||
timestamp: Date.now(), | |||
path: p.path, | |||
ref1: p.ref1, | |||
ref2: p.ref2, | |||
}; | |||
} | |||
void Container.context.workspaceState.update(WorkspaceState.ViewsSearchAndComparePinnedItems, savedPins); | |||
void Container.context.workspaceState.update(WorkspaceState.DeprecatedPinnedComparisons, undefined); | |||
} | |||
const root = this.ensureRoot(); | |||
return Object.values(savedPins) | |||
.sort((a, b) => (b.timestamp ?? 0) - (a.timestamp ?? 0)) | |||
.map(p => | |||
p.type === 'comparison' | |||
? new CompareResultsNode(this, root, p.path, p.ref1, p.ref2, p.timestamp) | |||
: new SearchResultsNode(this, root, p.path, p.search, p.labels, undefined, p.timestamp), | |||
); | |||
} | |||
async updatePinned(id: string, pin?: PinnedItem) { | |||
let pinned = Container.context.workspaceState.get<PinnedItems>(WorkspaceState.ViewsSearchAndComparePinnedItems); | |||
if (pinned == null) { | |||
pinned = Object.create(null) as PinnedItems; | |||
} | |||
if (pin != null) { | |||
pinned[id] = { ...pin }; | |||
} else { | |||
const { [id]: _, ...rest } = pinned; | |||
pinned = rest; | |||
} | |||
await Container.context.workspaceState.update(WorkspaceState.ViewsSearchAndComparePinnedItems, pinned); | |||
this.triggerNodeChange(this.ensureRoot()); | |||
} | |||
private async addResults( | |||
results: CompareResultsNode | SearchResultsNode, | |||
options: { | |||
expand?: boolean | number; | |||
focus?: boolean; | |||
select?: boolean; | |||
} = { expand: true, focus: true, select: true }, | |||
) { | |||
if (!this.visible) { | |||
await this.show(); | |||
} | |||
const root = this.ensureRoot(); | |||
root.addOrReplace(results, !this.keepResults); | |||
setImmediate(() => this.reveal(results, options)); | |||
} | |||
private edit(node: SearchResultsNode) { | |||
if (!(node instanceof SearchResultsNode)) return undefined; | |||
return node.edit(); | |||
} | |||
private setFilesLayout(layout: ViewFilesLayout) { | |||
return configuration.updateEffective('views', this.configKey, 'files', 'layout', layout); | |||
} | |||
private setKeepResults(enabled: boolean) { | |||
void Container.context.workspaceState.update(WorkspaceState.ViewsSearchAndCompareKeepResults, enabled); | |||
void setCommandContext(CommandContext.ViewsSearchAndCompareKeepResults, enabled); | |||
} | |||
private setShowAvatars(enabled: boolean) { | |||
return configuration.updateEffective('views', this.configKey, 'avatars', enabled); | |||
} | |||
private pin(node: CompareResultsNode | SearchResultsNode) { | |||
if (!(node instanceof CompareResultsNode) && !(node instanceof SearchResultsNode)) return undefined; | |||
return node.pin(); | |||
} | |||
private swapComparison(node: CompareResultsNode) { | |||
if (!(node instanceof CompareResultsNode)) return undefined; | |||
return node.swap(); | |||
} | |||
private unpin(node: CompareResultsNode | SearchResultsNode) { | |||
if (!(node instanceof CompareResultsNode) && !(node instanceof SearchResultsNode)) return undefined; | |||
return node.unpin(); | |||
} | |||
} |
@ -1,279 +0,0 @@ | |||
'use strict'; | |||
import { commands, ConfigurationChangeEvent } from 'vscode'; | |||
import { configuration, SearchViewConfig, ViewFilesLayout } from '../configuration'; | |||
import { CommandContext, setCommandContext, WorkspaceState } from '../constants'; | |||
import { Container } from '../container'; | |||
import { GitLog, SearchPattern } from '../git/git'; | |||
import { Functions, Strings } from '../system'; | |||
import { nodeSupportsConditionalDismissal, SearchNode, SearchResultsCommitsNode, ViewNode } from './nodes'; | |||
import { ViewBase } from './viewBase'; | |||
interface SearchQueryResults { | |||
readonly label: string; | |||
readonly log: GitLog | undefined; | |||
readonly hasMore: boolean; | |||
more?(limit: number | undefined): Promise<void>; | |||
} | |||
export class SearchView extends ViewBase<SearchNode, SearchViewConfig> { | |||
protected readonly configKey = 'search'; | |||
constructor() { | |||
super('gitlens.views.search', 'Search Commits'); | |||
void setCommandContext(CommandContext.ViewsSearchKeepResults, this.keepResults); | |||
} | |||
getRoot() { | |||
return new SearchNode(this); | |||
} | |||
protected registerCommands() { | |||
void Container.viewCommands; | |||
commands.registerCommand(this.getQualifiedCommand('clear'), () => this.clear(), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('copy'), | |||
() => commands.executeCommand('gitlens.views.copy', this.selection), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('refresh'), () => this.refresh(true), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToAuto'), | |||
() => this.setFilesLayout(ViewFilesLayout.Auto), | |||
this, | |||
); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToList'), | |||
() => this.setFilesLayout(ViewFilesLayout.List), | |||
this, | |||
); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setFilesLayoutToTree'), | |||
() => this.setFilesLayout(ViewFilesLayout.Tree), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('setKeepResultsToOn'), () => this.setKeepResults(true), this); | |||
commands.registerCommand( | |||
this.getQualifiedCommand('setKeepResultsToOff'), | |||
() => this.setKeepResults(false), | |||
this, | |||
); | |||
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOn'), () => this.setShowAvatars(true), this); | |||
commands.registerCommand(this.getQualifiedCommand('setShowAvatarsOff'), () => this.setShowAvatars(false), this); | |||
} | |||
protected filterConfigurationChanged(e: ConfigurationChangeEvent) { | |||
const changed = super.filterConfigurationChanged(e); | |||
if ( | |||
!changed && | |||
!configuration.changed(e, 'defaultDateFormat') && | |||
!configuration.changed(e, 'defaultDateSource') && | |||
!configuration.changed(e, 'defaultDateStyle') && | |||
!configuration.changed(e, 'defaultGravatarsStyle') | |||
) { | |||
return false; | |||
} | |||
return true; | |||
} | |||
get keepResults(): boolean { | |||
return Container.context.workspaceState.get<boolean>(WorkspaceState.ViewsSearchKeepResults, false); | |||
} | |||
clear() { | |||
this.root?.clear(); | |||
} | |||
dismissNode(node: ViewNode) { | |||
if (this.root == null) return; | |||
if (nodeSupportsConditionalDismissal(node) && node.canDismiss() === false) return; | |||
this.root.dismiss(node); | |||
} | |||
async search( | |||
repoPath: string, | |||
search: SearchPattern, | |||
{ | |||
label, | |||
reveal, | |||
...options | |||
}: { | |||
label: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
limit?: number; | |||
reveal?: { | |||
select?: boolean; | |||
focus?: boolean; | |||
expand?: boolean | number; | |||
}; | |||
}, | |||
results?: Promise<GitLog | undefined> | GitLog, | |||
) { | |||
if (!this.visible) { | |||
await this.show(); | |||
} | |||
const searchQueryFn = this.getSearchQueryFn( | |||
results ?? Container.git.getLogForSearch(repoPath, search, options), | |||
{ label: label }, | |||
); | |||
return this.addResults( | |||
new SearchResultsCommitsNode( | |||
this, | |||
this.root!, | |||
repoPath, | |||
search, | |||
`Results ${typeof label === 'string' ? label : label.label}`, | |||
searchQueryFn, | |||
), | |||
reveal, | |||
); | |||
} | |||
showSearchResults( | |||
repoPath: string, | |||
search: SearchPattern, | |||
log: GitLog, | |||
{ | |||
label, | |||
reveal, | |||
...options | |||
}: { | |||
label: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
limit?: number; | |||
reveal?: { | |||
select?: boolean; | |||
focus?: boolean; | |||
expand?: boolean | number; | |||
}; | |||
}, | |||
) { | |||
const labelString = this.getSearchLabel(label, log); | |||
const results: Mutable<Partial<SearchQueryResults>> = { | |||
label: labelString, | |||
log: log, | |||
hasMore: log.hasMore, | |||
}; | |||
if (results.hasMore) { | |||
results.more = async (limit: number | undefined) => { | |||
results.log = (await results.log?.more?.(limit)) ?? results.log; | |||
results.label = this.getSearchLabel(label, results.log); | |||
results.hasMore = results.log?.hasMore ?? true; | |||
}; | |||
} | |||
const searchQueryFn = Functions.cachedOnce( | |||
this.getSearchQueryFn(log, { label: label, ...options }), | |||
results as SearchQueryResults, | |||
); | |||
return this.addResults( | |||
new SearchResultsCommitsNode(this, this.root!, repoPath, search, labelString, searchQueryFn), | |||
reveal, | |||
); | |||
} | |||
private addResults( | |||
results: ViewNode, | |||
options: { | |||
select?: boolean; | |||
focus?: boolean; | |||
expand?: boolean | number; | |||
} = { select: true, expand: true }, | |||
) { | |||
const root = this.ensureRoot(); | |||
root.addOrReplace(results, !this.keepResults); | |||
setImmediate(() => void this.reveal(results, options)); | |||
} | |||
private getSearchLabel( | |||
label: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}, | |||
log: GitLog | undefined, | |||
) { | |||
if (typeof label === 'string') return label; | |||
const count = log?.count ?? 0; | |||
const resultsType = | |||
label.resultsType === undefined ? { singular: 'result', plural: 'results' } : label.resultsType; | |||
return `${Strings.pluralize(resultsType.singular, count, { | |||
number: log?.hasMore ?? false ? `${count}+` : undefined, | |||
plural: resultsType.plural, | |||
zero: 'No', | |||
})} ${label.label}`; | |||
} | |||
private getSearchQueryFn( | |||
logOrPromise: Promise<GitLog | undefined> | GitLog | undefined, | |||
options: { | |||
label: | |||
| string | |||
| { | |||
label: string; | |||
resultsType?: { singular: string; plural: string }; | |||
}; | |||
}, | |||
): (limit: number | undefined) => Promise<SearchQueryResults> { | |||
let useCacheOnce = true; | |||
return async (limit: number | undefined) => { | |||
let log = await logOrPromise; | |||
if (!useCacheOnce && log !== undefined && log.query !== undefined) { | |||
log = await log.query(limit); | |||
} | |||
useCacheOnce = false; | |||
const results: Mutable<Partial<SearchQueryResults>> = { | |||
label: this.getSearchLabel(options.label, log), | |||
log: log, | |||
hasMore: log?.hasMore, | |||
}; | |||
if (results.hasMore) { | |||
results.more = async (limit: number | undefined) => { | |||
results.log = (await results.log?.more?.(limit)) ?? results.log; | |||
results.label = this.getSearchLabel(options.label, results.log); | |||
results.hasMore = results.log?.hasMore ?? true; | |||
}; | |||
} | |||
return results as SearchQueryResults; | |||
}; | |||
} | |||
private setFilesLayout(layout: ViewFilesLayout) { | |||
return configuration.updateEffective('views', this.configKey, 'files', 'layout', layout); | |||
} | |||
private setKeepResults(enabled: boolean) { | |||
void Container.context.workspaceState.update(WorkspaceState.ViewsSearchKeepResults, enabled); | |||
void setCommandContext(CommandContext.ViewsSearchKeepResults, enabled); | |||
} | |||
private setShowAvatars(enabled: boolean) { | |||
return configuration.updateEffective('views', this.configKey, 'avatars', enabled); | |||
} | |||
} |
@ -1,135 +0,0 @@ | |||
<section id="search-commits-view" class="section--settings section--collapsible"> | |||
<div class="section__header"> | |||
<h2> | |||
Search Commits view | |||
<a | |||
class="link__learn-more" | |||
title="Learn more" | |||
href="https://github.com/eamodio/vscode-gitlens/tree/master/#search-commits-view-" | |||
> | |||
<i class="icon icon__info"></i> | |||
</a> | |||
</h2> | |||
<p class="section__header-hint"> | |||
Adds a Search Commits view to search and explore commit histories by message, author, files, id, etc | |||
</p> | |||
</div> | |||
<div class="section__collapsible"> | |||
<div class="section__group"> | |||
<div class="section__content"> | |||
<div class="settings settings--fixed ml-1"> | |||
<div class="section__group"> | |||
<div class="section__content"> | |||
<div class="settings settings--fixed"> | |||
<div class="setting"> | |||
<div class="setting__input"> | |||
<input | |||
id="views.search.pullRequests.enabled" | |||
name="views.search.pullRequests.enabled" | |||
type="checkbox" | |||
data-setting | |||
/> | |||
<label for="views.search.pullRequests.enabled" | |||
>Show associated Pull Requests</label | |||
> | |||
</div> | |||
<p class="setting__hint"> | |||
Requires a connection to a supported remote service (e.g. GitHub) | |||
</p> | |||
</div> | |||
<div class="settings settings--fixed ml-2"> | |||
<div class="setting" data-enablement="views.search.pullRequests.enabled"> | |||
<div class="setting__input"> | |||
<input | |||
id="views.search.pullRequests.showForCommits" | |||
name="views.search.pullRequests.showForCommits" | |||
type="checkbox" | |||
data-setting | |||
disabled | |||
/> | |||
<label for="views.search.pullRequests.showForCommits" | |||
>Show the PR that introduced each commit</label | |||
> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="setting"> | |||
<div class="setting__input"> | |||
<label for="views.search.files.layout">Layout files</label> | |||
<div class="select-container"> | |||
<select id="views.search.files.layout" name="views.search.files.layout" data-setting> | |||
<option value="auto">automatically</option> | |||
<option value="list">as a list</option> | |||
<option value="tree">as a tree</option> | |||
</select> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="setting"> | |||
<div class="setting__input"> | |||
<input | |||
id="views.search.files.compact" | |||
name="views.search.files.compact" | |||
type="checkbox" | |||
data-setting | |||
/> | |||
<label for="views.search.files.compact">Use compact file layout</label> | |||
</div> | |||
<p class="setting__hint">Compacts (flattens) unnecessary nesting when using a tree layouts</p> | |||
</div> | |||
<div class="setting"> | |||
<div class="setting__input"> | |||
<input id="views.search.avatars" name="views.search.avatars" type="checkbox" data-setting /> | |||
<label for="views.search.avatars">Use author avatars</label> | |||
</div> | |||
</div> | |||
</div> | |||
</div> | |||
<div class="section__preview"> | |||
<img | |||
class="image__preview hidden" | |||
src="#{root}/images/settings/view-search.png" | |||
data-visibility="views.search.files.layout !tree" | |||
/> | |||
<img | |||
class="image__preview hidden" | |||
src="#{root}/images/settings/view-search-tree-compact.png" | |||
data-visibility="views.search.files.layout =tree & views.search.files.compact" | |||
/> | |||
<img | |||
class="image__preview hidden" | |||
src="#{root}/images/settings/view-search-tree.png" | |||
data-visibility="views.search.files.layout =tree & views.search.files.compact =false" | |||
/> | |||
<img | |||
class="image__preview--overlay hidden" | |||
src="#{root}/images/settings/view-search-avatars.png" | |||
data-visibility="views.search.avatars" | |||
/> | |||
</div> | |||
</div> | |||
<div class="section__group"> | |||
<p class="section__hint"> | |||
<i class="icon icon__info"></i> For more options, open | |||
<a | |||
class="command" | |||
title="Open Settings" | |||
href="command:workbench.action.openSettings?%22gitlens.views.search%22" | |||
>Settings</a | |||
> | |||
and search for <b><i>gitlens.views.search</i></b> or <b><i>gitlens.views</i></b> | |||
</p> | |||
</div> | |||
</div> | |||
</section> |