SCM - more improvements to the repositories view (#274438)

* SCM - tweak artifact folder compression

* SCM - fixed more commands and layout

* SCM - more compression changes
This commit is contained in:
Ladislau Szomoru
2025-10-31 21:49:50 +00:00
committed by GitHub
parent ff488e306c
commit edf5868b02
4 changed files with 118 additions and 54 deletions

View File

@@ -496,7 +496,6 @@
{
"command": "git.branch",
"title": "%command.branch%",
"icon": "$(plus)",
"category": "Git",
"enablement": "!operationInProgress"
},
@@ -1046,6 +1045,20 @@
"title": "%command.graphCompareRef%",
"category": "Git",
"enablement": "!operationInProgress"
},
{
"command": "git.repositories.createBranch",
"title": "%command.branch%",
"icon": "$(plus)",
"category": "Git",
"enablement": "!operationInProgress"
},
{
"command": "git.repositories.createTag",
"title": "%command.createTag%",
"icon": "$(plus)",
"category": "Git",
"enablement": "!operationInProgress"
}
],
"continueEditSession": [
@@ -1681,6 +1694,14 @@
{
"command": "git.repositories.compareRef",
"when": "false"
},
{
"command": "git.repositories.createBranch",
"when": "false"
},
{
"command": "git.repositories.createTag",
"when": "false"
}
],
"scm/title": [
@@ -1873,12 +1894,12 @@
],
"scm/artifactGroup/context": [
{
"command": "git.branch",
"command": "git.repositories.createBranch",
"group": "inline@1",
"when": "scmProvider == git && scmArtifactGroup == branches"
},
{
"command": "git.createTag",
"command": "git.repositories.createTag",
"group": "inline@1",
"when": "scmProvider == git && scmArtifactGroup == tags"
}

View File

@@ -3363,24 +3363,7 @@ export class CommandCenter {
@command('git.createTag', { repository: true })
async createTag(repository: Repository, historyItem?: SourceControlHistoryItem): Promise<void> {
const inputTagName = await window.showInputBox({
placeHolder: l10n.t('Tag name'),
prompt: l10n.t('Please provide a tag name'),
ignoreFocusOut: true
});
if (!inputTagName) {
return;
}
const inputMessage = await window.showInputBox({
placeHolder: l10n.t('Message'),
prompt: l10n.t('Please provide a message to annotate the tag'),
ignoreFocusOut: true
});
const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
await repository.tag({ name, message: inputMessage, ref: historyItem?.id });
await this._createTag(repository, historyItem?.id);
}
@command('git.deleteTag', { repository: true })
@@ -5259,6 +5242,24 @@ export class CommandCenter {
config.update(setting, !enabled, true);
}
@command('git.repositories.createBranch', { repository: true })
async artifactGroupCreateBranch(repository: Repository): Promise<void> {
if (!repository) {
return;
}
await this._branch(repository, undefined, false);
}
@command('git.repositories.createTag', { repository: true })
async artifactGroupCreateTag(repository: Repository): Promise<void> {
if (!repository) {
return;
}
await this._createTag(repository);
}
@command('git.repositories.checkout', { repository: true })
async artifactCheckout(repository: Repository, artifact: SourceControlArtifact): Promise<void> {
if (!repository || !artifact) {
@@ -5312,6 +5313,27 @@ export class CommandCenter {
`${sourceRef.ref.name}${artifact.name}`);
}
private async _createTag(repository: Repository, ref?: string): Promise<void> {
const inputTagName = await window.showInputBox({
placeHolder: l10n.t('Tag name'),
prompt: l10n.t('Please provide a tag name'),
ignoreFocusOut: true
});
if (!inputTagName) {
return;
}
const inputMessage = await window.showInputBox({
placeHolder: l10n.t('Message'),
prompt: l10n.t('Please provide a message to annotate the tag'),
ignoreFocusOut: true
});
const name = inputTagName.replace(/^\.|\/\.|\.\.|~|\^|:|\/$|\.lock$|\.lock\/|\\|\*|\s|^\s*$|\.$/g, '-');
await repository.tag({ name, message: inputMessage, ref });
}
private createCommand(id: string, key: string, method: Function, options: ScmCommandOptions): (...args: any[]) => any {
const result = (...args: any[]) => {
let result: Promise<any>;

View File

@@ -538,6 +538,11 @@
.scm-repositories-view .scm-artifact-group,
.scm-repositories-view .scm-artifact {
display: flex;
align-items: center;
.icon {
margin-right: 2px;
}
}
.scm-repositories-view .scm-artifact-group .monaco-icon-label,
@@ -545,10 +550,6 @@
flex-grow: 1;
}
.scm-repositories-view .scm-artifact .monaco-icon-label-container {
display: flex;
}
.scm-repositories-view .scm-artifact-group .monaco-highlighted-label,
.scm-repositories-view .scm-artifact .monaco-highlighted-label {
display: flex;

View File

@@ -43,6 +43,7 @@ import { basename } from '../../../../base/common/resources.js';
import { ICompressibleTreeRenderer } from '../../../../base/browser/ui/tree/objectTree.js';
import { ICompressedTreeNode } from '../../../../base/browser/ui/tree/compressedObjectTreeModel.js';
import { ITreeCompressionDelegate } from '../../../../base/browser/ui/tree/asyncDataTree.js';
import { Codicon } from '../../../../base/common/codicons.js';
type TreeElement = ISCMRepository | SCMArtifactGroupTreeElement | SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>;
@@ -66,6 +67,7 @@ class ListDelegate implements IListVirtualDelegate<ISCMRepository> {
}
interface ArtifactGroupTemplate {
readonly icon: HTMLElement;
readonly label: IconLabel;
readonly actionBar: WorkbenchToolBar;
readonly elementDisposables: DisposableStore;
@@ -89,21 +91,23 @@ class ArtifactGroupRenderer implements ICompressibleTreeRenderer<SCMArtifactGrou
renderTemplate(container: HTMLElement): ArtifactGroupTemplate {
const element = append(container, $('.scm-artifact-group'));
const label = new IconLabel(element, { supportIcons: true });
const icon = append(element, $('.icon'));
const label = new IconLabel(element, { supportIcons: false });
const actionsContainer = append(element, $('.actions'));
const actionBar = new WorkbenchToolBar(actionsContainer, undefined, this._menuService, this._contextKeyService, this._contextMenuService, this._keybindingService, this._commandService, this._telemetryService);
return { label, actionBar, elementDisposables: new DisposableStore(), templateDisposable: combinedDisposable(label, actionBar) };
return { icon, label, actionBar, elementDisposables: new DisposableStore(), templateDisposable: combinedDisposable(label, actionBar) };
}
renderElement(node: ITreeNode<SCMArtifactGroupTreeElement, FuzzyScore>, index: number, templateData: ArtifactGroupTemplate): void {
const provider = node.element.repository.provider;
const artifactGroup = node.element.artifactGroup;
const artifactGroupIcon = ThemeIcon.isThemeIcon(artifactGroup.icon)
? `$(${artifactGroup.icon.id}) ` : '';
templateData.label.setLabel(`${artifactGroupIcon}${artifactGroup.name}`);
templateData.icon.className = ThemeIcon.isThemeIcon(artifactGroup.icon)
? `icon ${ThemeIcon.asClassName(artifactGroup.icon)}`
: '';
templateData.label.setLabel(artifactGroup.name);
const repositoryMenus = this._scmViewService.menus.getRepositoryMenus(provider);
templateData.elementDisposables.add(connectPrimaryMenu(repositoryMenus.getArtifactGroupMenu(artifactGroup), primary => {
@@ -127,6 +131,7 @@ class ArtifactGroupRenderer implements ICompressibleTreeRenderer<SCMArtifactGrou
}
interface ArtifactTemplate {
readonly icon: HTMLElement;
readonly label: IconLabel;
readonly actionBar: WorkbenchToolBar;
readonly elementDisposables: DisposableStore;
@@ -150,12 +155,13 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
renderTemplate(container: HTMLElement): ArtifactTemplate {
const element = append(container, $('.scm-artifact'));
const label = new IconLabel(element, { supportIcons: true });
const icon = append(element, $('.icon'));
const label = new IconLabel(element, { supportIcons: false });
const actionsContainer = append(element, $('.actions'));
const actionBar = new WorkbenchToolBar(actionsContainer, undefined, this._menuService, this._contextKeyService, this._contextMenuService, this._keybindingService, this._commandService, this._telemetryService);
return { label, actionBar, elementDisposables: new DisposableStore(), templateDisposable: combinedDisposable(label, actionBar) };
return { icon, label, actionBar, elementDisposables: new DisposableStore(), templateDisposable: combinedDisposable(label, actionBar) };
}
renderElement(nodeOrElement: ITreeNode<SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>, FuzzyScore>, index: number, templateData: ArtifactTemplate): void {
@@ -163,19 +169,21 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
if (isSCMArtifactNode(artifactOrFolder)) {
// Folder
templateData.label.setLabel(`$(folder) ${basename(artifactOrFolder.uri)}`);
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
templateData.label.setLabel(basename(artifactOrFolder.uri));
templateData.actionBar.setActions([]);
templateData.actionBar.context = undefined;
} else {
// Artifact
const artifact = artifactOrFolder.artifact;
const artifactIcon = ThemeIcon.isThemeIcon(artifactOrFolder.group.icon)
? `$(${artifactOrFolder.group.icon.id}) `
templateData.icon.className = ThemeIcon.isThemeIcon(artifactOrFolder.group.icon)
? `icon ${ThemeIcon.asClassName(artifactOrFolder.group.icon)}`
: '';
const artifactLabel = artifact.name.split('/').pop() ?? artifact.name;
templateData.label.setLabel(`${artifactIcon}${artifactLabel}`, artifact.description);
templateData.label.setLabel(artifactLabel, artifact.description);
const provider = artifactOrFolder.repository.provider;
const repositoryMenus = this._scmViewService.menus.getRepositoryMenus(provider);
@@ -187,13 +195,31 @@ class ArtifactRenderer implements ICompressibleTreeRenderer<SCMArtifactTreeEleme
}
renderCompressedElements(node: ITreeNode<ICompressedTreeNode<SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>>, FuzzyScore>, index: number, templateData: ArtifactTemplate, details?: ITreeElementRenderDetails): void {
const compressed = node.element as ICompressedTreeNode<IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>>;
const folder = compressed.elements[compressed.elements.length - 1];
templateData.label.setLabel(`$(folder) ${folder.uri.fsPath.substring(1)}`);
const compressed = node.element;
const artifactOrFolder = compressed.elements[compressed.elements.length - 1];
if (isSCMArtifactTreeElement(artifactOrFolder)) {
const artifact = artifactOrFolder.artifact;
templateData.icon.className = ThemeIcon.isThemeIcon(artifactOrFolder.group.icon)
? `icon ${ThemeIcon.asClassName(artifactOrFolder.group.icon)}`
: '';
templateData.label.setLabel(artifact.name, artifact.description);
const provider = artifactOrFolder.repository.provider;
const repositoryMenus = this._scmViewService.menus.getRepositoryMenus(provider);
templateData.elementDisposables.add(connectPrimaryMenu(repositoryMenus.getArtifactMenu(artifactOrFolder.group), primary => {
templateData.actionBar.setActions(primary);
}, 'inline', provider));
templateData.actionBar.context = artifact;
} else if (ResourceTree.isResourceNode(artifactOrFolder)) {
templateData.icon.className = `icon ${ThemeIcon.asClassName(Codicon.folder)}`;
templateData.label.setLabel(artifactOrFolder.uri.fsPath.substring(1));
templateData.actionBar.setActions([]);
templateData.actionBar.context = undefined;
}
}
disposeElement(element: ITreeNode<SCMArtifactTreeElement | IResourceNode<SCMArtifactTreeElement, SCMArtifactGroupTreeElement>, FuzzyScore>, index: number, templateData: ArtifactTemplate, details?: ITreeElementRenderDetails): void {
templateData.elementDisposables.clear();
@@ -306,12 +332,12 @@ class RepositoryTreeIdentityProvider implements IIdentityProvider<TreeElement> {
class RepositoriesTreeCompressionDelegate implements ITreeCompressionDelegate<TreeElement> {
isIncompressible(element: TreeElement): boolean {
if (ResourceTree.isResourceNode(element)) {
return element.childrenCount === 0 || !element.parent || !element.parent.parent;
}
return element.childrenCount > 1;
} else {
return true;
}
}
}
export class SCMRepositoriesViewPane extends ViewPane {
@@ -440,18 +466,12 @@ export class SCMRepositoriesViewPane extends ViewPane {
}
// Explorer mode
// Expand artifact folders with one child only
if (isSCMArtifactNode(e)) {
if (e.childrenCount !== 1) {
// Only expand artifact folders as they are compressed by default
return !(e.childrenCount === 1 && Iterable.first(e.children)?.element === undefined);
} else {
return true;
}
// Check if the only child is a leaf node
const firstChild = Iterable.first(e.children);
return firstChild?.element !== undefined;
}
return true;
},
compressionEnabled: true,
overrideStyles: this.getLocationBasedColors().listOverrideStyles,