mirror of
https://github.com/Microsoft/vscode
synced 2024-08-27 13:00:58 +00:00
Merge branch 'master' into sandy081/smoketests/remote
This commit is contained in:
commit
3bd244bcf2
|
@ -1230,6 +1230,10 @@ export function asCSSUrl(uri: URI): string {
|
|||
return `url('${FileAccess.asBrowserUri(uri).toString(true).replace(/'/g, '%27')}')`;
|
||||
}
|
||||
|
||||
export function asCSSPropertyValue(value: string) {
|
||||
return `'${value.replace(/'/g, '%27')}'`;
|
||||
}
|
||||
|
||||
export function triggerDownload(dataOrUri: Uint8Array | URI, name: string): void {
|
||||
|
||||
// If the data is provided as Buffer, we create a
|
||||
|
|
|
@ -6,8 +6,7 @@
|
|||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CSSIcon } from 'vs/base/common/codicons';
|
||||
|
||||
const labelWithIconsRegex = /(\\)?\$\(([a-z\-]+(?:~[a-z\-]+)?)\)/gi;
|
||||
|
||||
const labelWithIconsRegex = new RegExp(`(\\\\)?\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)`, 'g');
|
||||
export function renderLabelWithIcons(text: string): Array<HTMLSpanElement | string> {
|
||||
const elements = new Array<HTMLSpanElement | string>();
|
||||
let match: RegExpMatchArray | null;
|
||||
|
|
|
@ -71,21 +71,25 @@ export interface CSSIcon {
|
|||
readonly id: string;
|
||||
}
|
||||
|
||||
|
||||
export namespace CSSIcon {
|
||||
export const iconIdRegex = /^(codicon\/)?([a-z\-]+)(?:~([a-z\-]+))?$/i;
|
||||
export const iconNameExpression = '[A-Za-z0-9\\-]+';
|
||||
export const iconModifierExpression = '~[A-Za-z]+';
|
||||
|
||||
const cssIconIdRegex = new RegExp(`^(${iconNameExpression})(${iconModifierExpression})?$`);
|
||||
|
||||
export function asClassNameArray(icon: CSSIcon): string[] {
|
||||
if (icon instanceof Codicon) {
|
||||
return ['codicon', 'codicon-' + icon.id];
|
||||
}
|
||||
const match = iconIdRegex.exec(icon.id);
|
||||
const match = cssIconIdRegex.exec(icon.id);
|
||||
if (!match) {
|
||||
return asClassNameArray(Codicon.error);
|
||||
}
|
||||
let [, , id, modifier] = match;
|
||||
let [, id, modifier] = match;
|
||||
const classNames = ['codicon', 'codicon-' + id];
|
||||
if (modifier) {
|
||||
classNames.push('codicon-modifier-' + modifier);
|
||||
classNames.push('codicon-modifier-' + modifier.substr(1));
|
||||
}
|
||||
return classNames;
|
||||
}
|
||||
|
|
|
@ -3,28 +3,26 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { CSSIcon } from 'vs/base/common/codicons';
|
||||
import { matchesFuzzy, IMatch } from 'vs/base/common/filters';
|
||||
import { ltrim } from 'vs/base/common/strings';
|
||||
|
||||
export const iconStartMarker = '$(';
|
||||
|
||||
const escapeIconsRegex = /(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
const iconsRegex = new RegExp(`\\$\\(${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?\\)`, 'g'); // no capturing groups
|
||||
|
||||
const escapeIconsRegex = new RegExp(`(\\\\)?${iconsRegex.source}`, 'g');
|
||||
export function escapeIcons(text: string): string {
|
||||
return text.replace(escapeIconsRegex, (match, escaped) => escaped ? match : `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownEscapedIconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
const markdownEscapedIconsRegex = new RegExp(`\\\\${iconsRegex.source}`, 'g');
|
||||
export function markdownEscapeEscapedIcons(text: string): string {
|
||||
// Need to add an extra \ for escaping in markdown
|
||||
return text.replace(markdownEscapedIconsRegex, match => `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownUnescapeIconsRegex = /(\\)?\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi;
|
||||
export function markdownUnescapeIcons(text: string): string {
|
||||
return text.replace(markdownUnescapeIconsRegex, (match, escaped, iconId) => escaped ? match : `$(${iconId})`);
|
||||
}
|
||||
|
||||
const stripIconsRegex = /(\s)?(\\)?\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)(\s)?/gi;
|
||||
const stripIconsRegex = new RegExp(`(\\s)?(\\\\)?${iconsRegex.source}(\\s)?`, 'g');
|
||||
export function stripIcons(text: string): string {
|
||||
if (text.indexOf(iconStartMarker) === -1) {
|
||||
return text;
|
||||
|
|
|
@ -71,6 +71,14 @@ export namespace Iterable {
|
|||
}
|
||||
}
|
||||
|
||||
export function reduce<T, R>(iterable: Iterable<T>, reducer: (previousValue: R, currentValue: T) => R, initialValue: R): R {
|
||||
let value = initialValue;
|
||||
for (const element of iterable) {
|
||||
value = reducer(value, element);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an iterable slice of the array, with the same semantics as `array.slice()`.
|
||||
*/
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
|
||||
import * as assert from 'assert';
|
||||
import { IMatch } from 'vs/base/common/filters';
|
||||
import { matchesFuzzyIconAware, parseLabelWithIcons, IParsedLabelWithIcons, stripIcons } from 'vs/base/common/iconLabels';
|
||||
import { matchesFuzzyIconAware, parseLabelWithIcons, IParsedLabelWithIcons, stripIcons, escapeIcons, markdownEscapeEscapedIcons } from 'vs/base/common/iconLabels';
|
||||
|
||||
export interface IIconFilter {
|
||||
// Returns null if word doesn't match.
|
||||
|
@ -71,4 +71,18 @@ suite('Icon Labels', () => {
|
|||
assert.strictEqual(stripIcons('$(Hello) World'), ' World');
|
||||
assert.strictEqual(stripIcons('$(Hello) W$(oi)rld'), ' Wrld');
|
||||
});
|
||||
|
||||
|
||||
test('escapeIcons', () => {
|
||||
assert.strictEqual(escapeIcons('Hello World'), 'Hello World');
|
||||
assert.strictEqual(escapeIcons('$(Hello World'), '$(Hello World');
|
||||
assert.strictEqual(escapeIcons('$(Hello) World'), '\\$(Hello) World');
|
||||
assert.strictEqual(escapeIcons('\\$(Hello) W$(oi)rld'), '\\$(Hello) W\\$(oi)rld');
|
||||
});
|
||||
|
||||
test('markdownEscapeEscapedIcons', () => {
|
||||
assert.strictEqual(markdownEscapeEscapedIcons('Hello World'), 'Hello World');
|
||||
assert.strictEqual(markdownEscapeEscapedIcons('$(Hello) World'), '$(Hello) World');
|
||||
assert.strictEqual(markdownEscapeEscapedIcons('\\$(Hello) World'), '\\\\$(Hello) World');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -16,7 +16,7 @@ import { ColorIdentifier, Extensions, IColorRegistry } from 'vs/platform/theme/c
|
|||
import { Extensions as ThemingExtensions, ICssStyleCollector, IFileIconTheme, IThemingRegistry, ITokenStyle } from 'vs/platform/theme/common/themeService';
|
||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { ColorScheme } from 'vs/platform/theme/common/theme';
|
||||
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet';
|
||||
|
||||
const VS_THEME_NAME = 'vs';
|
||||
const VS_DARK_THEME_NAME = 'vs-dark';
|
||||
|
@ -214,9 +214,9 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon
|
|||
this._knownThemes.set(VS_DARK_THEME_NAME, newBuiltInTheme(VS_DARK_THEME_NAME));
|
||||
this._knownThemes.set(HC_BLACK_THEME_NAME, newBuiltInTheme(HC_BLACK_THEME_NAME));
|
||||
|
||||
const iconRegistry = getIconRegistry();
|
||||
const iconsStyleSheet = getIconsStyleSheet();
|
||||
|
||||
this._codiconCSS = iconRegistry.getCSS();
|
||||
this._codiconCSS = iconsStyleSheet.getCSS();
|
||||
this._themeCSS = '';
|
||||
this._allCSS = `${this._codiconCSS}\n${this._themeCSS}`;
|
||||
this._globalStyleElement = null;
|
||||
|
@ -224,8 +224,8 @@ export class StandaloneThemeServiceImpl extends Disposable implements IStandalon
|
|||
this._colorMapOverride = null;
|
||||
this.setTheme(VS_THEME_NAME);
|
||||
|
||||
iconRegistry.onDidChange(() => {
|
||||
this._codiconCSS = iconRegistry.getCSS();
|
||||
iconsStyleSheet.onDidChange(() => {
|
||||
this._codiconCSS = iconsStyleSheet.getCSS();
|
||||
this._updateCSS();
|
||||
});
|
||||
}
|
||||
|
|
61
src/vs/platform/theme/browser/iconsStyleSheet.ts
Normal file
61
src/vs/platform/theme/browser/iconsStyleSheet.ts
Normal file
|
@ -0,0 +1,61 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { getIconRegistry, IconContribution, IconFontContribution } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { asCSSPropertyValue, asCSSUrl } from 'vs/base/browser/dom';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
|
||||
|
||||
export interface IIconsStyleSheet {
|
||||
getCSS(): string;
|
||||
readonly onDidChange: Event<void>;
|
||||
}
|
||||
|
||||
export function getIconsStyleSheet(): IIconsStyleSheet {
|
||||
const onDidChangeEmmiter = new Emitter<void>();
|
||||
const iconRegistry = getIconRegistry();
|
||||
iconRegistry.onDidChange(() => onDidChangeEmmiter.fire());
|
||||
|
||||
return {
|
||||
onDidChange: onDidChangeEmmiter.event,
|
||||
getCSS() {
|
||||
const usedFontIds: { [id: string]: IconFontContribution } = {};
|
||||
const formatIconRule = (contribution: IconContribution): string | undefined => {
|
||||
let definition = contribution.defaults;
|
||||
while (ThemeIcon.isThemeIcon(definition)) {
|
||||
const c = iconRegistry.getIcon(definition.id);
|
||||
if (!c) {
|
||||
return undefined;
|
||||
}
|
||||
definition = c.defaults;
|
||||
}
|
||||
const fontId = definition.fontId;
|
||||
if (fontId) {
|
||||
const fontContribution = iconRegistry.getIconFont(fontId);
|
||||
if (fontContribution) {
|
||||
usedFontIds[fontId] = fontContribution;
|
||||
return `.codicon-${contribution.id}:before { content: '${definition.character}'; font-family: ${asCSSPropertyValue(fontId)}; }`;
|
||||
}
|
||||
}
|
||||
return `.codicon-${contribution.id}:before { content: '${definition.character}'; }`;
|
||||
};
|
||||
|
||||
const rules = [];
|
||||
for (let contribution of iconRegistry.getIcons()) {
|
||||
const rule = formatIconRule(contribution);
|
||||
if (rule) {
|
||||
rules.push(rule);
|
||||
}
|
||||
}
|
||||
for (let id in usedFontIds) {
|
||||
const fontContribution = usedFontIds[id];
|
||||
const src = fontContribution.definition.src.map(l => `${asCSSUrl(l.location)} format('${l.format}')`).join(', ');
|
||||
rules.push(`@font-face { src: ${src}; font-family: ${asCSSPropertyValue(id)}; }`);
|
||||
}
|
||||
return rules.join('\n');
|
||||
}
|
||||
};
|
||||
}
|
|
@ -11,11 +11,11 @@ import { localize } from 'vs/nls';
|
|||
import { Extensions as JSONExtensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
|
||||
import { RunOnceScheduler } from 'vs/base/common/async';
|
||||
import * as Codicons from 'vs/base/common/codicons';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
|
||||
// ------ API types
|
||||
|
||||
|
||||
// color registry
|
||||
// icon registry
|
||||
export const Extensions = {
|
||||
IconContribution: 'base.contributions.icons'
|
||||
};
|
||||
|
@ -34,6 +34,15 @@ export interface IconContribution {
|
|||
defaults: IconDefaults;
|
||||
}
|
||||
|
||||
export interface IconFontContribution {
|
||||
id: string;
|
||||
definition: IconFontDefinition;
|
||||
}
|
||||
|
||||
export interface IconFontDefinition {
|
||||
src: { location: URI, format: string; }[]
|
||||
}
|
||||
|
||||
export interface IIconRegistry {
|
||||
|
||||
readonly onDidChange: Event<void>;
|
||||
|
@ -42,12 +51,12 @@ export interface IIconRegistry {
|
|||
* Register a icon to the registry.
|
||||
* @param id The icon id
|
||||
* @param defaults The default values
|
||||
* @description the description
|
||||
* @param description The description
|
||||
*/
|
||||
registerIcon(id: string, defaults: IconDefaults, description?: string): ThemeIcon;
|
||||
|
||||
/**
|
||||
* Register a icon to the registry.
|
||||
* Deregister a icon from the registry.
|
||||
*/
|
||||
deregisterIcon(id: string): void;
|
||||
|
||||
|
@ -62,7 +71,7 @@ export interface IIconRegistry {
|
|||
getIcon(id: string): IconContribution | undefined;
|
||||
|
||||
/**
|
||||
* JSON schema for an object to assign icon values to one of the color contributions.
|
||||
* JSON schema for an object to assign icon values to one of the icon contributions.
|
||||
*/
|
||||
getIconSchema(): IJSONSchema;
|
||||
|
||||
|
@ -72,10 +81,26 @@ export interface IIconRegistry {
|
|||
getIconReferenceSchema(): IJSONSchema;
|
||||
|
||||
/**
|
||||
* The CSS for all icons
|
||||
* Register a icon font to the registry.
|
||||
* @param id The icon font id
|
||||
* @param definition The iocn font definition
|
||||
*/
|
||||
getCSS(): string;
|
||||
registerIconFont(id: string, definition: IconFontDefinition): IconFontContribution;
|
||||
|
||||
/**
|
||||
* Deregister an icon font to the registry.
|
||||
*/
|
||||
deregisterIconFont(id: string): void;
|
||||
|
||||
/**
|
||||
* Get all icon font contributions
|
||||
*/
|
||||
getIconFonts(): IconFontContribution[];
|
||||
|
||||
/**
|
||||
* Get the icon font for the given id
|
||||
*/
|
||||
getIconFont(id: string): IconFontContribution | undefined;
|
||||
}
|
||||
|
||||
class IconRegistry implements IIconRegistry {
|
||||
|
@ -99,10 +124,13 @@ class IconRegistry implements IIconRegistry {
|
|||
type: 'object',
|
||||
properties: {}
|
||||
};
|
||||
private iconReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', enum: [], enumDescriptions: [] };
|
||||
private iconReferenceSchema: IJSONSchema & { enum: string[], enumDescriptions: string[] } = { type: 'string', pattern: `^${Codicons.CSSIcon.iconNameExpression}$`, enum: [], enumDescriptions: [] };
|
||||
|
||||
private iconFontsById: { [key: string]: IconFontContribution };
|
||||
|
||||
constructor() {
|
||||
this.iconsById = {};
|
||||
this.iconFontsById = {};
|
||||
}
|
||||
|
||||
public registerIcon(id: string, defaults: IconDefaults, description?: string, deprecationMessage?: string): ThemeIcon {
|
||||
|
@ -164,27 +192,27 @@ class IconRegistry implements IIconRegistry {
|
|||
return this.iconReferenceSchema;
|
||||
}
|
||||
|
||||
public getCSS() {
|
||||
const rules = [];
|
||||
for (let id in this.iconsById) {
|
||||
const rule = this.formatRule(id);
|
||||
if (rule) {
|
||||
rules.push(rule);
|
||||
}
|
||||
public registerIconFont(id: string, definition: IconFontDefinition): IconFontContribution {
|
||||
const existing = this.iconFontsById[id];
|
||||
if (existing) {
|
||||
return existing;
|
||||
}
|
||||
return rules.join('\n');
|
||||
let iconFontContribution: IconFontContribution = { id, definition };
|
||||
this.iconFontsById[id] = iconFontContribution;
|
||||
this._onDidChange.fire();
|
||||
return iconFontContribution;
|
||||
}
|
||||
|
||||
private formatRule(id: string): string | undefined {
|
||||
let definition = this.iconsById[id].defaults;
|
||||
while (ThemeIcon.isThemeIcon(definition)) {
|
||||
const c = this.iconsById[definition.id];
|
||||
if (!c) {
|
||||
return undefined;
|
||||
}
|
||||
definition = c.defaults;
|
||||
}
|
||||
return `.codicon-${id}:before { content: '${definition.character}'; }`;
|
||||
public deregisterIconFont(id: string): void {
|
||||
delete this.iconFontsById[id];
|
||||
}
|
||||
|
||||
public getIconFonts(): IconFontContribution[] {
|
||||
return Object.keys(this.iconFontsById).map(id => this.iconFontsById[id]);
|
||||
}
|
||||
|
||||
public getIconFont(id: string): IconFontContribution | undefined {
|
||||
return this.iconFontsById[id];
|
||||
}
|
||||
|
||||
public toString() {
|
||||
|
|
|
@ -40,18 +40,15 @@ export namespace ThemeIcon {
|
|||
return obj && typeof obj === 'object' && typeof (<ThemeIcon>obj).id === 'string' && (typeof (<ThemeIcon>obj).color === 'undefined' || ThemeColor.isThemeColor((<ThemeIcon>obj).color));
|
||||
}
|
||||
|
||||
const _regexFromString = /^\$\(([a-z.]+\/)?([a-z-~]+)\)$/i;
|
||||
const _regexFromString = new RegExp(`^\\$\\((${CSSIcon.iconNameExpression}(?:${CSSIcon.iconModifierExpression})?)\\)$`);
|
||||
|
||||
export function fromString(str: string): ThemeIcon | undefined {
|
||||
const match = _regexFromString.exec(str);
|
||||
if (!match) {
|
||||
return undefined;
|
||||
}
|
||||
let [, owner, name] = match;
|
||||
if (!owner || owner === 'codicon/') {
|
||||
return { id: name };
|
||||
}
|
||||
return { id: owner + name };
|
||||
let [, name] = match;
|
||||
return { id: name };
|
||||
}
|
||||
|
||||
export function modify(icon: ThemeIcon, modifier: 'disabled' | 'spin' | undefined): ThemeIcon {
|
||||
|
|
3
src/vs/vscode.proposed.d.ts
vendored
3
src/vs/vscode.proposed.d.ts
vendored
|
@ -1486,9 +1486,8 @@ declare module 'vscode' {
|
|||
export class NotebookCellOutput {
|
||||
|
||||
readonly outputs: NotebookCellOutputItem[];
|
||||
readonly metadata?: Record<string, string | number | boolean>;
|
||||
|
||||
constructor(outputs: NotebookCellOutputItem[], metadata?: Record<string, string | number | boolean>);
|
||||
constructor(outputs: NotebookCellOutputItem[]);
|
||||
|
||||
//TODO@jrieken HACK to workaround dependency issues...
|
||||
toJSON(): any;
|
||||
|
|
|
@ -11,6 +11,7 @@ import { LifecyclePhase } from 'vs/workbench/services/lifecycle/common/lifecycle
|
|||
// --- other interested parties
|
||||
import { JSONValidationExtensionPoint } from 'vs/workbench/api/common/jsonValidationExtensionPoint';
|
||||
import { ColorExtensionPoint } from 'vs/workbench/services/themes/common/colorExtensionPoint';
|
||||
import { IconExtensionPoint, IconFontExtensionPoint } from 'vs/workbench/services/themes/common/iconExtensionPoint';
|
||||
import { TokenClassificationExtensionPoints } from 'vs/workbench/services/themes/common/tokenClassificationExtensionPoint';
|
||||
import { LanguageConfigurationFileHandler } from 'vs/workbench/contrib/codeEditor/browser/languageConfigurationExtensionPoint';
|
||||
|
||||
|
@ -79,6 +80,8 @@ export class ExtensionPoints implements IWorkbenchContribution {
|
|||
// Classes that handle extension points...
|
||||
this.instantiationService.createInstance(JSONValidationExtensionPoint);
|
||||
this.instantiationService.createInstance(ColorExtensionPoint);
|
||||
this.instantiationService.createInstance(IconExtensionPoint);
|
||||
this.instantiationService.createInstance(IconFontExtensionPoint);
|
||||
this.instantiationService.createInstance(TokenClassificationExtensionPoints);
|
||||
this.instantiationService.createInstance(LanguageConfigurationFileHandler);
|
||||
}
|
||||
|
|
|
@ -2826,10 +2826,7 @@ export class NotebookCellOutput {
|
|||
return obj instanceof NotebookCellOutput;
|
||||
}
|
||||
|
||||
constructor(
|
||||
readonly outputs: NotebookCellOutputItem[],
|
||||
readonly metadata?: Record<string, string | number | boolean>
|
||||
) { }
|
||||
constructor(readonly outputs: NotebookCellOutputItem[]) { }
|
||||
|
||||
toJSON(): IDisplayOutput {
|
||||
let data: { [key: string]: unknown; } = {};
|
||||
|
|
|
@ -214,7 +214,9 @@ function findVisibleNeighbour(layoutService: IWorkbenchLayoutService, part: Part
|
|||
}
|
||||
|
||||
function focusNextOrPreviousPart(layoutService: IWorkbenchLayoutService, editorService: IEditorService, next: boolean): void {
|
||||
const editorFocused = editorService.activeEditorPane?.hasFocus();
|
||||
// Need to ask if the active editor has focus since the layoutService is not aware of some custom editor focus behavior(notebooks)
|
||||
// Also need to ask the layoutService for the case if no editor is opened
|
||||
const editorFocused = editorService.activeEditorPane?.hasFocus() || layoutService.hasFocus(Parts.EDITOR_PART);
|
||||
const currentlyFocusedPart = editorFocused ? Parts.EDITOR_PART : layoutService.hasFocus(Parts.ACTIVITYBAR_PART) ? Parts.ACTIVITYBAR_PART :
|
||||
layoutService.hasFocus(Parts.STATUSBAR_PART) ? Parts.STATUSBAR_PART : layoutService.hasFocus(Parts.SIDEBAR_PART) ? Parts.SIDEBAR_PART : layoutService.hasFocus(Parts.PANEL_PART) ? Parts.PANEL_PART : undefined;
|
||||
let partToFocus = Parts.EDITOR_PART;
|
||||
|
|
|
@ -342,10 +342,11 @@ export class Workbench extends Layout {
|
|||
// Create Parts
|
||||
[
|
||||
{ id: Parts.TITLEBAR_PART, role: 'contentinfo', classes: ['titlebar'] },
|
||||
{ id: Parts.ACTIVITYBAR_PART, role: 'navigation', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },
|
||||
{ id: Parts.SIDEBAR_PART, role: 'complementary', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },
|
||||
// Use role 'none' for some parts to make screen readers less chatty #114892
|
||||
{ id: Parts.ACTIVITYBAR_PART, role: 'none', classes: ['activitybar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },
|
||||
{ id: Parts.SIDEBAR_PART, role: 'none', classes: ['sidebar', this.state.sideBar.position === Position.LEFT ? 'left' : 'right'] },
|
||||
{ id: Parts.EDITOR_PART, role: 'main', classes: ['editor'], options: { restorePreviousState: this.state.editor.restoreEditors } },
|
||||
{ id: Parts.PANEL_PART, role: 'complementary', classes: ['panel', positionToString(this.state.panel.position)] },
|
||||
{ id: Parts.PANEL_PART, role: 'none', classes: ['panel', positionToString(this.state.panel.position)] },
|
||||
{ id: Parts.STATUSBAR_PART, role: 'status', classes: ['statusbar'] }
|
||||
].forEach(({ id, role, classes, options }) => {
|
||||
const partContainer = this.createPart(id, role, classes);
|
||||
|
|
|
@ -10,7 +10,7 @@ import { Event } from 'vs/base/common/event';
|
|||
import { VIEW_PANE_ID, ISCMService, ISCMRepository, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IActivityService, NumberBadge } from 'vs/workbench/services/activity/common/activity';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IStatusbarService, StatusbarAlignment as MainThreadStatusBarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -30,7 +30,6 @@ export class SCMStatusController implements IWorkbenchContribution {
|
|||
private statusBarDisposable: IDisposable = Disposable.None;
|
||||
private focusDisposable: IDisposable = Disposable.None;
|
||||
private focusedRepository: ISCMRepository | undefined = undefined;
|
||||
private focusedProviderContextKey: IContextKey<string | undefined>;
|
||||
private readonly badgeDisposable = new MutableDisposable<IDisposable>();
|
||||
private disposables = new DisposableStore();
|
||||
private repositoryDisposables = new Set<IDisposable>();
|
||||
|
@ -45,7 +44,6 @@ export class SCMStatusController implements IWorkbenchContribution {
|
|||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
|
||||
) {
|
||||
this.focusedProviderContextKey = contextKeyService.createKey<string | undefined>('scmProvider', undefined);
|
||||
this.scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
|
||||
this.scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
|
||||
|
||||
|
@ -126,7 +124,6 @@ export class SCMStatusController implements IWorkbenchContribution {
|
|||
|
||||
this.focusDisposable.dispose();
|
||||
this.focusedRepository = repository;
|
||||
this.focusedProviderContextKey.set(repository && repository.provider.id);
|
||||
|
||||
if (repository && repository.provider.onDidChangeStatusBarCommands) {
|
||||
this.focusDisposable = repository.provider.onDidChangeStatusBarCommands(() => this.renderStatusBar(repository));
|
||||
|
|
|
@ -13,7 +13,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
|
|||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { WorkbenchList } from 'vs/platform/list/browser/listService';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -22,7 +21,7 @@ import { SIDE_BAR_BACKGROUND } from 'vs/workbench/common/theme';
|
|||
import { IOpenerService } from 'vs/platform/opener/common/opener';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { RepositoryRenderer } from 'vs/workbench/contrib/scm/browser/scmRepositoryRenderer';
|
||||
import { collectContextMenuActions, StatusBarAction, StatusBarActionViewItem } from 'vs/workbench/contrib/scm/browser/util';
|
||||
import { collectContextMenuActions, getStatusBarActionViewItem } from 'vs/workbench/contrib/scm/browser/util';
|
||||
import { Orientation } from 'vs/base/browser/ui/sash/sash';
|
||||
|
||||
class ListDelegate implements IListVirtualDelegate<ISCMRepository> {
|
||||
|
@ -63,7 +62,7 @@ export class SCMRepositoriesViewPane extends ViewPane {
|
|||
const listContainer = append(container, $('.scm-view.scm-repositories-view'));
|
||||
|
||||
const delegate = new ListDelegate();
|
||||
const renderer = this.instantiationService.createInstance(RepositoryRenderer, a => this.getActionViewItem(a),);
|
||||
const renderer = this.instantiationService.createInstance(RepositoryRenderer, getStatusBarActionViewItem);
|
||||
const identityProvider = { getId: (r: ISCMRepository) => r.provider.id };
|
||||
|
||||
this.list = this.instantiationService.createInstance(WorkbenchList, `SCM Main`, listContainer, delegate, [renderer], {
|
||||
|
@ -192,12 +191,4 @@ export class SCMRepositoriesViewPane extends ViewPane {
|
|||
this.list.setFocus([selection[0]]);
|
||||
}
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (action instanceof StatusBarAction) {
|
||||
return new StatusBarActionViewItem(action);
|
||||
}
|
||||
|
||||
return super.getActionViewItem(action);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,23 +7,23 @@ import 'vs/css!./media/scm';
|
|||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { basename, dirname } from 'vs/base/common/resources';
|
||||
import { IDisposable, Disposable, DisposableStore, combinedDisposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { ViewPane, IViewPaneOptions, ViewAction } from 'vs/workbench/browser/parts/views/viewPane';
|
||||
import { append, $, Dimension, asCSSUrl } from 'vs/base/browser/dom';
|
||||
import { IListVirtualDelegate, IIdentityProvider } from 'vs/base/browser/ui/list/list';
|
||||
import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ISCMResourceGroup, ISCMResource, InputValidationType, ISCMRepository, ISCMInput, IInputValidation, ISCMViewService, ISCMViewVisibleRepositoryChangeEvent, ISCMService, SCMInputChangeReason, VIEW_PANE_ID } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ResourceLabels, IResourceLabel } from 'vs/workbench/browser/labels';
|
||||
import { CountBadge } from 'vs/base/browser/ui/countBadge/countBadge';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IContextViewService, IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IContextKeyService, IContextKey, ContextKeyExpr, RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { MenuItemAction, IMenuService } from 'vs/platform/actions/common/actions';
|
||||
import { IAction, IActionViewItem, ActionRunner, Action, RadioGroup, Separator, SubmenuAction, IActionViewItemProvider } from 'vs/base/common/actions';
|
||||
import { MenuItemAction, IMenuService, registerAction2, MenuId, IAction2Options, MenuRegistry, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IAction, ActionRunner, IActionViewItemProvider } from 'vs/base/common/actions';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IThemeService, registerThemingParticipant, IFileIconTheme, ThemeIcon } from 'vs/platform/theme/common/themeService';
|
||||
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, StatusBarAction, StatusBarActionViewItem, getRepositoryVisibilityActions } from './util';
|
||||
import { IThemeService, registerThemingParticipant, IFileIconTheme } from 'vs/platform/theme/common/themeService';
|
||||
import { isSCMResource, isSCMResourceGroup, connectPrimaryMenuToInlineActionBar, isSCMRepository, isSCMInput, collectContextMenuActions, getStatusBarActionViewItem } from './util';
|
||||
import { attachBadgeStyler } from 'vs/platform/theme/common/styler';
|
||||
import { WorkbenchCompressibleObjectTree, IOpenEvent } from 'vs/platform/list/browser/listService';
|
||||
import { IConfigurationService, ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||
|
@ -79,6 +79,7 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur
|
|||
import { LabelFuzzyScore } from 'vs/base/browser/ui/tree/abstractTree';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
import { API_OPEN_DIFF_EDITOR_COMMAND_ID, API_OPEN_EDITOR_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
|
||||
type TreeElement = ISCMRepository | ISCMInput | ISCMResourceGroup | IResourceNode<ISCMResource, ISCMResourceGroup> | ISCMResource;
|
||||
|
||||
|
@ -760,9 +761,149 @@ const enum ViewModelMode {
|
|||
}
|
||||
|
||||
const enum ViewModelSortKey {
|
||||
Path,
|
||||
Name,
|
||||
Status
|
||||
Path = 'path',
|
||||
Name = 'name',
|
||||
Status = 'status'
|
||||
}
|
||||
|
||||
const Menus = {
|
||||
ViewSort: new MenuId('SCMViewSort'),
|
||||
Repositories: new MenuId('SCMRepositories'),
|
||||
};
|
||||
|
||||
const ContextKeys = {
|
||||
ViewModelMode: new RawContextKey<ViewModelMode>('scmViewModelMode', ViewModelMode.List),
|
||||
ViewModelSortKey: new RawContextKey<ViewModelSortKey>('scmViewModelSortKey', ViewModelSortKey.Path),
|
||||
ViewModelAreAllRepositoriesCollapsed: new RawContextKey<boolean>('scmViewModelAreAllRepositoriesCollapsed', false),
|
||||
ViewModelIsAnyRepositoryCollapsible: new RawContextKey<boolean>('scmViewModelIsAnyRepositoryCollapsible', false),
|
||||
SCMProvider: new RawContextKey<string | undefined>('scmProvider', undefined),
|
||||
SCMProviderRootUri: new RawContextKey<string | undefined>('scmProviderRootUri', undefined),
|
||||
SCMProviderHasRootUri: new RawContextKey<boolean>('scmProviderHasRootUri', undefined),
|
||||
RepositoryCount: new RawContextKey<number>('scmRepositoryCount', 0),
|
||||
RepositoryVisibilityCount: new RawContextKey<number>('scmRepositoryVisibleCount', 0),
|
||||
RepositoryVisibility(repository: ISCMRepository) {
|
||||
return new RawContextKey<boolean>(`scmRepositoryVisible:${repository.provider.id}`, false);
|
||||
}
|
||||
};
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.SCMTitle, {
|
||||
title: localize('sortAction', "View & Sort"),
|
||||
submenu: Menus.ViewSort,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0)),
|
||||
group: '0_view&sort'
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(Menus.ViewSort, {
|
||||
title: localize('repositories', "Repositories"),
|
||||
submenu: Menus.Repositories,
|
||||
group: '0_repositories'
|
||||
});
|
||||
|
||||
class RepositoryVisibilityAction extends Action2 {
|
||||
|
||||
private repository: ISCMRepository;
|
||||
|
||||
constructor(repository: ISCMRepository) {
|
||||
const title = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label;
|
||||
super({
|
||||
id: `workbench.scm.action.toggleRepositoryVisibility.${repository.provider.id}`,
|
||||
title,
|
||||
f1: false,
|
||||
precondition: ContextKeyExpr.or(ContextKeys.RepositoryVisibilityCount.notEqualsTo(1), ContextKeys.RepositoryVisibility(repository).isEqualTo(false)),
|
||||
toggled: ContextKeys.RepositoryVisibility(repository).isEqualTo(true),
|
||||
menu: { id: Menus.Repositories }
|
||||
});
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
run(accessor: ServicesAccessor) {
|
||||
const scmViewService = accessor.get(ISCMViewService);
|
||||
scmViewService.toggleVisibility(this.repository);
|
||||
}
|
||||
}
|
||||
|
||||
interface RepositoryVisibilityItem {
|
||||
readonly contextKey: IContextKey<boolean>;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
class RepositoryVisibilityActionController {
|
||||
|
||||
private items = new Map<ISCMRepository, RepositoryVisibilityItem>();
|
||||
private repositoryCountContextKey: IContextKey<number>;
|
||||
private repositoryVisibilityCountContextKey: IContextKey<number>;
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
constructor(
|
||||
@ISCMViewService private scmViewService: ISCMViewService,
|
||||
@ISCMService scmService: ISCMService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService
|
||||
) {
|
||||
this.repositoryCountContextKey = ContextKeys.RepositoryCount.bindTo(contextKeyService);
|
||||
this.repositoryVisibilityCountContextKey = ContextKeys.RepositoryVisibilityCount.bindTo(contextKeyService);
|
||||
|
||||
scmViewService.onDidChangeVisibleRepositories(this.onDidChangeVisibleRepositories, this, this.disposables);
|
||||
scmService.onDidAddRepository(this.onDidAddRepository, this, this.disposables);
|
||||
scmService.onDidRemoveRepository(this.onDidRemoveRepository, this, this.disposables);
|
||||
|
||||
for (const repository of scmService.repositories) {
|
||||
this.onDidAddRepository(repository);
|
||||
}
|
||||
}
|
||||
|
||||
private onDidAddRepository(repository: ISCMRepository): void {
|
||||
const action = registerAction2(class extends RepositoryVisibilityAction {
|
||||
constructor() {
|
||||
super(repository);
|
||||
}
|
||||
});
|
||||
|
||||
const contextKey = ContextKeys.RepositoryVisibility(repository).bindTo(this.contextKeyService);
|
||||
contextKey.set(this.scmViewService.isVisible(repository));
|
||||
|
||||
this.items.set(repository, {
|
||||
contextKey,
|
||||
dispose() {
|
||||
contextKey.reset();
|
||||
action.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
this.updateRepositoriesCounts();
|
||||
}
|
||||
|
||||
private onDidRemoveRepository(repository: ISCMRepository): void {
|
||||
this.items.get(repository)?.dispose();
|
||||
this.items.delete(repository);
|
||||
this.updateRepositoriesCounts();
|
||||
}
|
||||
|
||||
private onDidChangeVisibleRepositories(): void {
|
||||
let count = 0;
|
||||
|
||||
for (const [repository, item] of this.items) {
|
||||
const isVisible = this.scmViewService.isVisible(repository);
|
||||
item.contextKey.set(isVisible);
|
||||
|
||||
if (isVisible) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
this.repositoryCountContextKey.set(this.items.size);
|
||||
this.repositoryVisibilityCountContextKey.set(count);
|
||||
}
|
||||
|
||||
private updateRepositoriesCounts(): void {
|
||||
this.repositoryCountContextKey.set(this.items.size);
|
||||
this.repositoryVisibilityCountContextKey.set(Iterable.reduce(this.items.keys(), (r, repository) => r + (this.scmViewService.isVisible(repository) ? 1 : 0), 0));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposables.dispose();
|
||||
dispose(this.items.values());
|
||||
this.items.clear();
|
||||
}
|
||||
}
|
||||
|
||||
class ViewModel {
|
||||
|
@ -770,12 +911,14 @@ class ViewModel {
|
|||
private readonly _onDidChangeMode = new Emitter<ViewModelMode>();
|
||||
readonly onDidChangeMode = this._onDidChangeMode.event;
|
||||
|
||||
private _onDidChangeRepositoryCollapseState = new Emitter<void>();
|
||||
readonly onDidChangeRepositoryCollapseState: Event<void>;
|
||||
private visible: boolean = false;
|
||||
|
||||
get mode(): ViewModelMode { return this._mode; }
|
||||
set mode(mode: ViewModelMode) {
|
||||
if (this._mode === mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._mode = mode;
|
||||
|
||||
for (const [, item] of this.items) {
|
||||
|
@ -792,14 +935,17 @@ class ViewModel {
|
|||
|
||||
this.refresh();
|
||||
this._onDidChangeMode.fire(mode);
|
||||
this.modeContextKey.set(mode);
|
||||
}
|
||||
|
||||
private _sortKey: ViewModelSortKey = ViewModelSortKey.Path;
|
||||
get sortKey(): ViewModelSortKey { return this._sortKey; }
|
||||
set sortKey(sortKey: ViewModelSortKey) {
|
||||
if (sortKey !== this._sortKey) {
|
||||
this._sortKey = sortKey;
|
||||
this.refresh();
|
||||
}
|
||||
this.sortKeyContextKey.set(sortKey);
|
||||
}
|
||||
|
||||
private _treeViewStateIsStale = false;
|
||||
|
@ -817,29 +963,44 @@ class ViewModel {
|
|||
private scrollTop: number | undefined;
|
||||
private alwaysShowRepositories = false;
|
||||
private firstVisible = true;
|
||||
private viewSubMenuAction: SCMViewSubMenuAction | undefined;
|
||||
private disposables = new DisposableStore();
|
||||
|
||||
private modeContextKey: IContextKey<ViewModelMode>;
|
||||
private sortKeyContextKey: IContextKey<ViewModelSortKey>;
|
||||
private areAllRepositoriesCollapsedContextKey: IContextKey<boolean>;
|
||||
private isAnyRepositoryCollapsibleContextKey: IContextKey<boolean>;
|
||||
private scmProviderContextKey: IContextKey<string | undefined>;
|
||||
private scmProviderRootUriContextKey: IContextKey<string | undefined>;
|
||||
private scmProviderHasRootUriContextKey: IContextKey<boolean>;
|
||||
|
||||
constructor(
|
||||
private tree: WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>,
|
||||
private inputRenderer: InputRenderer,
|
||||
private _mode: ViewModelMode,
|
||||
private _sortKey: ViewModelSortKey,
|
||||
private _treeViewState: ITreeViewState | undefined,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IEditorService protected editorService: IEditorService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService,
|
||||
@ISCMViewService private scmViewService: ISCMViewService,
|
||||
@IUriIdentityService private uriIdentityService: IUriIdentityService
|
||||
@IUriIdentityService private uriIdentityService: IUriIdentityService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
this.onDidChangeRepositoryCollapseState = Event.any(
|
||||
this._onDidChangeRepositoryCollapseState.event,
|
||||
Event.signal(Event.filter(this.tree.onDidChangeCollapseState, e => isSCMRepository(e.node.element)))
|
||||
);
|
||||
this.modeContextKey = ContextKeys.ViewModelMode.bindTo(contextKeyService);
|
||||
this.modeContextKey.set(_mode);
|
||||
this.sortKeyContextKey = ContextKeys.ViewModelSortKey.bindTo(contextKeyService);
|
||||
this.sortKeyContextKey.set(this._sortKey);
|
||||
this.areAllRepositoriesCollapsedContextKey = ContextKeys.ViewModelAreAllRepositoriesCollapsed.bindTo(contextKeyService);
|
||||
this.isAnyRepositoryCollapsibleContextKey = ContextKeys.ViewModelIsAnyRepositoryCollapsible.bindTo(contextKeyService);
|
||||
this.scmProviderContextKey = ContextKeys.SCMProvider.bindTo(contextKeyService);
|
||||
this.scmProviderRootUriContextKey = ContextKeys.SCMProviderRootUri.bindTo(contextKeyService);
|
||||
this.scmProviderHasRootUriContextKey = ContextKeys.SCMProviderHasRootUri.bindTo(contextKeyService);
|
||||
|
||||
configurationService.onDidChangeConfiguration(this.onDidChangeConfiguration, this, this.disposables);
|
||||
this.onDidChangeConfiguration();
|
||||
|
||||
Event.filter(this.tree.onDidChangeCollapseState, e => isSCMRepository(e.node.element))
|
||||
(this.updateRepositoryCollapseAllContextKeys, this, this.disposables);
|
||||
|
||||
this.disposables.add(this.tree.onDidChangeCollapseState(() => this._treeViewStateIsStale = true));
|
||||
}
|
||||
|
||||
|
@ -950,10 +1111,21 @@ class ViewModel {
|
|||
}
|
||||
|
||||
this.visible = visible;
|
||||
this._onDidChangeRepositoryCollapseState.fire();
|
||||
this.updateRepositoryCollapseAllContextKeys();
|
||||
}
|
||||
|
||||
private refresh(item?: IRepositoryItem | IGroupItem): void {
|
||||
if (!this.alwaysShowRepositories && this.items.size === 1) {
|
||||
const provider = Iterable.first(this.items.values())!.element.provider;
|
||||
this.scmProviderContextKey.set(provider.contextValue);
|
||||
this.scmProviderRootUriContextKey.set(provider.rootUri?.toString());
|
||||
this.scmProviderHasRootUriContextKey.set(!!provider.rootUri);
|
||||
} else {
|
||||
this.scmProviderContextKey.set(undefined);
|
||||
this.scmProviderRootUriContextKey.set(undefined);
|
||||
this.scmProviderHasRootUriContextKey.set(false);
|
||||
}
|
||||
|
||||
if (!this.alwaysShowRepositories && (this.items.size === 1 && (!item || isRepositoryItem(item)))) {
|
||||
const item = Iterable.first(this.items.values())!;
|
||||
this.tree.setChildren(null, this.render(item, this.treeViewState).children);
|
||||
|
@ -964,7 +1136,7 @@ class ViewModel {
|
|||
this.tree.setChildren(null, items.map(item => this.render(item, this.treeViewState)));
|
||||
}
|
||||
|
||||
this._onDidChangeRepositoryCollapseState.fire();
|
||||
this.updateRepositoryCollapseAllContextKeys();
|
||||
}
|
||||
|
||||
private render(item: IRepositoryItem | IGroupItem, treeViewState?: ITreeViewState): ICompressedTreeElement<TreeElement> {
|
||||
|
@ -1067,56 +1239,18 @@ class ViewModel {
|
|||
this.tree.domFocus();
|
||||
}
|
||||
|
||||
getViewActions(): IAction[] {
|
||||
if (this.scmViewService.visibleRepositories.length === 0) {
|
||||
return this.scmViewService.menus.titleMenu.actions;
|
||||
private updateRepositoryCollapseAllContextKeys(): void {
|
||||
if (!this.visible || this.scmViewService.visibleRepositories.length === 1) {
|
||||
this.isAnyRepositoryCollapsibleContextKey.set(false);
|
||||
this.areAllRepositoriesCollapsedContextKey.set(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (this.alwaysShowRepositories || this.scmViewService.visibleRepositories.length !== 1) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const menus = this.scmViewService.menus.getRepositoryMenus(this.scmViewService.visibleRepositories[0].provider);
|
||||
return menus.titleMenu.actions;
|
||||
this.isAnyRepositoryCollapsibleContextKey.set(this.scmViewService.visibleRepositories.some(r => this.tree.hasElement(r) && this.tree.isCollapsible(r)));
|
||||
this.areAllRepositoriesCollapsedContextKey.set(this.scmViewService.visibleRepositories.every(r => this.tree.hasElement(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r))));
|
||||
}
|
||||
|
||||
getViewSecondaryActions(): IAction[] {
|
||||
if (this.scmViewService.visibleRepositories.length === 0) {
|
||||
return this.scmViewService.menus.titleMenu.secondaryActions;
|
||||
}
|
||||
|
||||
if (!this.viewSubMenuAction) {
|
||||
this.viewSubMenuAction = this.instantiationService.createInstance(SCMViewSubMenuAction, this);
|
||||
this.disposables.add(this.viewSubMenuAction);
|
||||
}
|
||||
|
||||
if (this.alwaysShowRepositories || this.scmViewService.visibleRepositories.length !== 1) {
|
||||
return this.viewSubMenuAction.actions.slice(0);
|
||||
}
|
||||
|
||||
const menus = this.scmViewService.menus.getRepositoryMenus(this.scmViewService.visibleRepositories[0].provider);
|
||||
const secondaryActions = menus.titleMenu.secondaryActions;
|
||||
|
||||
if (secondaryActions.length === 0) {
|
||||
return [this.viewSubMenuAction];
|
||||
}
|
||||
|
||||
return [this.viewSubMenuAction, new Separator(), ...secondaryActions];
|
||||
}
|
||||
|
||||
getViewActionsContext(): any {
|
||||
if (this.scmViewService.visibleRepositories.length === 0) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (this.alwaysShowRepositories || this.scmViewService.visibleRepositories.length !== 1) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return this.scmViewService.visibleRepositories[0].provider;
|
||||
}
|
||||
|
||||
collapseAllProviders(): void {
|
||||
collapseAllRepositories(): void {
|
||||
for (const repository of this.scmViewService.visibleRepositories) {
|
||||
if (this.tree.isCollapsible(repository)) {
|
||||
this.tree.collapse(repository);
|
||||
|
@ -1124,7 +1258,7 @@ class ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
expandAllProviders(): void {
|
||||
expandAllRepositories(): void {
|
||||
for (const repository of this.scmViewService.visibleRepositories) {
|
||||
if (this.tree.isCollapsible(repository)) {
|
||||
this.tree.expand(repository);
|
||||
|
@ -1132,22 +1266,6 @@ class ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
isAnyProviderCollapsible(): boolean {
|
||||
if (!this.visible || this.scmViewService.visibleRepositories.length === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.scmViewService.visibleRepositories.some(r => this.tree.hasElement(r) && this.tree.isCollapsible(r));
|
||||
}
|
||||
|
||||
areAllProvidersCollapsed(): boolean {
|
||||
if (!this.visible || this.scmViewService.visibleRepositories.length === 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.scmViewService.visibleRepositories.every(r => this.tree.hasElement(r) && (!this.tree.isCollapsible(r) || this.tree.isCollapsed(r)));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.visibilityDisposables.dispose();
|
||||
this.disposables.dispose();
|
||||
|
@ -1156,145 +1274,155 @@ class ViewModel {
|
|||
}
|
||||
}
|
||||
|
||||
class SCMViewRepositoriesSubMenuAction extends SubmenuAction {
|
||||
|
||||
get actions(): IAction[] {
|
||||
return getRepositoryVisibilityActions(this.scmService, this.scmViewService);
|
||||
class SetListViewModeAction extends ViewAction<SCMViewPane> {
|
||||
constructor(menu: Partial<IAction2Options['menu']> = {}) {
|
||||
super({
|
||||
id: 'workbench.scm.action.setListViewMode',
|
||||
title: localize('setListViewMode', "View as List"),
|
||||
viewId: VIEW_PANE_ID,
|
||||
f1: false,
|
||||
icon: Codicon.listFlat,
|
||||
toggled: ContextKeys.ViewModelMode.isEqualTo(ViewModelMode.List),
|
||||
menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu }
|
||||
});
|
||||
}
|
||||
|
||||
constructor(
|
||||
@ISCMService private readonly scmService: ISCMService,
|
||||
@ISCMViewService private readonly scmViewService: ISCMViewService,
|
||||
) {
|
||||
super('scm.repositories', localize('repositories', "Repositories"), []);
|
||||
async runInView(_: ServicesAccessor, view: SCMViewPane): Promise<void> {
|
||||
view.viewModel.mode = ViewModelMode.List;
|
||||
}
|
||||
}
|
||||
|
||||
class SCMViewSubMenuAction extends SubmenuAction implements IDisposable {
|
||||
|
||||
private disposable: IDisposable;
|
||||
|
||||
constructor(
|
||||
viewModel: ViewModel,
|
||||
@IInstantiationService instantiationService: IInstantiationService
|
||||
) {
|
||||
const listAction = new SCMViewModeListAction(viewModel);
|
||||
const treeAction = new SCMViewModeTreeAction(viewModel);
|
||||
const sortByNameAction = new SCMSortByNameAction(viewModel);
|
||||
const sortByPathAction = new SCMSortByPathAction(viewModel);
|
||||
const sortByStatusAction = new SCMSortByStatusAction(viewModel);
|
||||
const actions = [
|
||||
instantiationService.createInstance(SCMViewRepositoriesSubMenuAction),
|
||||
new Separator(),
|
||||
...new RadioGroup([listAction, treeAction]).actions,
|
||||
new Separator(),
|
||||
...new RadioGroup([sortByNameAction, sortByPathAction, sortByStatusAction]).actions
|
||||
];
|
||||
|
||||
super(
|
||||
'scm.viewsort',
|
||||
localize('sortAction', "View & Sort"),
|
||||
actions
|
||||
);
|
||||
|
||||
this.disposable = combinedDisposable(listAction, treeAction, sortByNameAction, sortByPathAction, sortByStatusAction);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.disposable.dispose();
|
||||
class SetListViewModeNavigationAction extends SetListViewModeAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: MenuId.SCMTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.ViewModelMode.isEqualTo(ViewModelMode.Tree)),
|
||||
group: 'navigation',
|
||||
order: -1000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class ToggleViewModeAction extends Action {
|
||||
|
||||
static readonly ID = 'workbench.scm.action.toggleViewMode';
|
||||
static readonly LABEL = localize('toggleViewMode', "Toggle View Mode");
|
||||
|
||||
constructor(id: string = ToggleViewModeAction.ID, label: string = ToggleViewModeAction.LABEL, private viewModel: ViewModel, private mode?: ViewModelMode) {
|
||||
super(id, label);
|
||||
this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this));
|
||||
this.onDidChangeMode(this.viewModel.mode);
|
||||
class SetTreeViewModeAction extends ViewAction<SCMViewPane> {
|
||||
constructor(menu: Partial<IAction2Options['menu']> = {}) {
|
||||
super({
|
||||
id: 'workbench.scm.action.setTreeViewMode',
|
||||
title: localize('setTreeViewMode', "View as Tree"),
|
||||
viewId: VIEW_PANE_ID,
|
||||
f1: false,
|
||||
icon: Codicon.listTree,
|
||||
toggled: ContextKeys.ViewModelMode.isEqualTo(ViewModelMode.Tree),
|
||||
menu: { id: Menus.ViewSort, group: '1_viewmode', ...menu }
|
||||
});
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (typeof this.mode === 'undefined') {
|
||||
this.viewModel.mode = this.viewModel.mode === ViewModelMode.List ? ViewModelMode.Tree : ViewModelMode.List;
|
||||
} else {
|
||||
this.viewModel.mode = this.mode;
|
||||
}
|
||||
}
|
||||
|
||||
private onDidChangeMode(mode: ViewModelMode): void {
|
||||
const iconClass = ThemeIcon.asClassName(mode === ViewModelMode.List ? Codicon.listTree : Codicon.listFlat);
|
||||
this.class = `scm-action toggle-view-mode ${iconClass}`;
|
||||
this.checked = this.viewModel.mode === this.mode;
|
||||
async runInView(_: ServicesAccessor, view: SCMViewPane): Promise<void> {
|
||||
view.viewModel.mode = ViewModelMode.Tree;
|
||||
}
|
||||
}
|
||||
|
||||
class SCMViewModeListAction extends ToggleViewModeAction {
|
||||
constructor(viewModel: ViewModel) {
|
||||
super('workbench.scm.action.viewModeList', localize('viewModeList', "View as List"), viewModel, ViewModelMode.List);
|
||||
class SetTreeViewModeNavigationAction extends SetTreeViewModeAction {
|
||||
constructor() {
|
||||
super({
|
||||
id: MenuId.SCMTitle,
|
||||
when: ContextKeyExpr.and(ContextKeyExpr.equals('view', VIEW_PANE_ID), ContextKeys.RepositoryCount.notEqualsTo(0), ContextKeys.ViewModelMode.isEqualTo(ViewModelMode.List)),
|
||||
group: 'navigation',
|
||||
order: -1000
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class SCMViewModeTreeAction extends ToggleViewModeAction {
|
||||
constructor(viewModel: ViewModel) {
|
||||
super('workbench.scm.action.viewModeTree', localize('viewModeTree', "View as Tree"), viewModel, ViewModelMode.Tree);
|
||||
registerAction2(SetListViewModeAction);
|
||||
registerAction2(SetTreeViewModeAction);
|
||||
registerAction2(SetListViewModeNavigationAction);
|
||||
registerAction2(SetTreeViewModeNavigationAction);
|
||||
|
||||
abstract class SetSortKeyAction extends ViewAction<SCMViewPane> {
|
||||
constructor(private sortKey: ViewModelSortKey, title: string) {
|
||||
super({
|
||||
id: `workbench.scm.action.setSortKey.${sortKey}`,
|
||||
title: title,
|
||||
viewId: VIEW_PANE_ID,
|
||||
f1: false,
|
||||
toggled: ContextKeys.ViewModelSortKey.isEqualTo(sortKey),
|
||||
menu: { id: Menus.ViewSort, group: '2_sort' }
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(_: ServicesAccessor, view: SCMViewPane): Promise<void> {
|
||||
view.viewModel.sortKey = this.sortKey;
|
||||
}
|
||||
}
|
||||
|
||||
abstract class SCMSortAction extends Action {
|
||||
|
||||
private readonly _listener: IDisposable;
|
||||
|
||||
constructor(id: string, label: string, private viewModel: ViewModel, private sortKey: ViewModelSortKey) {
|
||||
super(id, label);
|
||||
|
||||
this.checked = this.sortKey === ViewModelSortKey.Path;
|
||||
this.enabled = this.viewModel?.mode === ViewModelMode.List ?? false;
|
||||
this._listener = viewModel?.onDidChangeMode(e => this.enabled = e === ViewModelMode.List);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this.sortKey !== this.viewModel.sortKey) {
|
||||
this.checked = !this.checked;
|
||||
this.viewModel.sortKey = this.sortKey;
|
||||
}
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this._listener.dispose();
|
||||
super.dispose();
|
||||
class SetSortByNameAction extends SetSortKeyAction {
|
||||
constructor() {
|
||||
super(ViewModelSortKey.Name, localize('sortByName', "Sort by Name"));
|
||||
}
|
||||
}
|
||||
|
||||
class SCMSortByNameAction extends SCMSortAction {
|
||||
static readonly ID = 'workbench.scm.action.sortByName';
|
||||
static readonly LABEL = localize('sortByName', "Sort by Name");
|
||||
|
||||
constructor(viewModel: ViewModel) {
|
||||
super(SCMSortByNameAction.ID, SCMSortByNameAction.LABEL, viewModel, ViewModelSortKey.Name);
|
||||
class SetSortByPathAction extends SetSortKeyAction {
|
||||
constructor() {
|
||||
super(ViewModelSortKey.Path, localize('sortByPath', "Sort by Path"));
|
||||
}
|
||||
}
|
||||
|
||||
class SCMSortByPathAction extends SCMSortAction {
|
||||
static readonly ID = 'workbench.scm.action.sortByPath';
|
||||
static readonly LABEL = localize('sortByPath', "Sort by Path");
|
||||
|
||||
constructor(viewModel: ViewModel) {
|
||||
super(SCMSortByPathAction.ID, SCMSortByPathAction.LABEL, viewModel, ViewModelSortKey.Path);
|
||||
class SetSortByStatusAction extends SetSortKeyAction {
|
||||
constructor() {
|
||||
super(ViewModelSortKey.Status, localize('sortByStatus', "Sort by Status"));
|
||||
}
|
||||
}
|
||||
|
||||
class SCMSortByStatusAction extends SCMSortAction {
|
||||
static readonly ID = 'workbench.scm.action.sortByStatus';
|
||||
static readonly LABEL = localize('sortByStatus', "Sort by Status");
|
||||
registerAction2(SetSortByNameAction);
|
||||
registerAction2(SetSortByPathAction);
|
||||
registerAction2(SetSortByStatusAction);
|
||||
|
||||
constructor(viewModel: ViewModel) {
|
||||
super(SCMSortByStatusAction.ID, SCMSortByStatusAction.LABEL, viewModel, ViewModelSortKey.Status);
|
||||
class CollapseAllRepositoriesAction extends ViewAction<SCMViewPane> {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.scm.action.collapseAllRepositories`,
|
||||
title: localize('collapse all', "Collapse All Repositories"),
|
||||
viewId: VIEW_PANE_ID,
|
||||
f1: false,
|
||||
icon: Codicon.collapseAll,
|
||||
menu: {
|
||||
id: MenuId.SCMTitle,
|
||||
group: 'navigation',
|
||||
when: ContextKeyExpr.and(ContextKeys.ViewModelIsAnyRepositoryCollapsible.isEqualTo(true), ContextKeys.ViewModelAreAllRepositoriesCollapsed.isEqualTo(false))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(_: ServicesAccessor, view: SCMViewPane): Promise<void> {
|
||||
view.viewModel.collapseAllRepositories();
|
||||
}
|
||||
}
|
||||
|
||||
class ExpandAllRepositoriesAction extends ViewAction<SCMViewPane> {
|
||||
|
||||
constructor() {
|
||||
super({
|
||||
id: `workbench.scm.action.expandAllRepositories`,
|
||||
title: localize('expand all', "Expand All Repositories"),
|
||||
viewId: VIEW_PANE_ID,
|
||||
f1: false,
|
||||
icon: Codicon.expandAll,
|
||||
menu: {
|
||||
id: MenuId.SCMTitle,
|
||||
group: 'navigation',
|
||||
when: ContextKeyExpr.and(ContextKeys.ViewModelIsAnyRepositoryCollapsible.isEqualTo(true), ContextKeys.ViewModelAreAllRepositoriesCollapsed.isEqualTo(true))
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async runInView(_: ServicesAccessor, view: SCMViewPane): Promise<void> {
|
||||
view.viewModel.expandAllRepositories();
|
||||
}
|
||||
}
|
||||
|
||||
registerAction2(CollapseAllRepositoriesAction);
|
||||
registerAction2(ExpandAllRepositoriesAction);
|
||||
|
||||
class SCMInputWidget extends Disposable {
|
||||
|
||||
private readonly defaultInputFontFamily = DEFAULT_FONT_FAMILY;
|
||||
|
@ -1630,73 +1758,65 @@ class SCMInputWidget extends Disposable {
|
|||
}
|
||||
}
|
||||
|
||||
class SCMCollapseAction extends Action {
|
||||
|
||||
private allCollapsed = false;
|
||||
|
||||
constructor(private viewModel: ViewModel) {
|
||||
super('scm.collapse', undefined, undefined, true);
|
||||
this._register(viewModel.onDidChangeRepositoryCollapseState(this.update, this));
|
||||
this.update();
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
if (this.allCollapsed) {
|
||||
this.viewModel.expandAllProviders();
|
||||
} else {
|
||||
this.viewModel.collapseAllProviders();
|
||||
}
|
||||
}
|
||||
|
||||
private update(): void {
|
||||
const isAnyProviderCollapsible = this.viewModel.isAnyProviderCollapsible();
|
||||
|
||||
this.enabled = isAnyProviderCollapsible;
|
||||
this.allCollapsed = isAnyProviderCollapsible && this.viewModel.areAllProvidersCollapsed();
|
||||
this.label = this.allCollapsed ? localize('expand all', "Expand All Repositories") : localize('collapse all', "Collapse All Repositories");
|
||||
this.class = ThemeIcon.asClassName(this.allCollapsed ? Codicon.expandAll : Codicon.collapseAll);
|
||||
}
|
||||
}
|
||||
|
||||
export class SCMViewPane extends ViewPane {
|
||||
|
||||
private _onDidLayout = new Emitter<void>();
|
||||
private layoutCache: ISCMLayout = {
|
||||
height: undefined,
|
||||
width: undefined,
|
||||
onDidChange: this._onDidLayout.event
|
||||
};
|
||||
private _onDidLayout: Emitter<void>;
|
||||
private layoutCache: ISCMLayout;
|
||||
|
||||
private listContainer!: HTMLElement;
|
||||
private tree!: WorkbenchCompressibleObjectTree<TreeElement, FuzzyScore>;
|
||||
private viewModel!: ViewModel;
|
||||
private _viewModel!: ViewModel;
|
||||
get viewModel(): ViewModel { return this._viewModel; }
|
||||
private listLabels!: ResourceLabels;
|
||||
private inputRenderer!: InputRenderer;
|
||||
private toggleViewModelModeAction: ToggleViewModeAction | undefined;
|
||||
|
||||
private scmService: ISCMService;
|
||||
private scmViewService: ISCMViewService;
|
||||
private storageService: IStorageService;
|
||||
private commandService: ICommandService;
|
||||
private editorService: IEditorService;
|
||||
private menuService: IMenuService;
|
||||
|
||||
constructor(
|
||||
options: IViewPaneOptions,
|
||||
@ISCMService private scmService: ISCMService,
|
||||
@ISCMViewService private scmViewService: ISCMViewService,
|
||||
@IKeybindingService protected keybindingService: IKeybindingService,
|
||||
@IThemeService protected themeService: IThemeService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IContextViewService protected contextViewService: IContextViewService,
|
||||
@ICommandService protected commandService: ICommandService,
|
||||
@IEditorService protected editorService: IEditorService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@ISCMService scmService: ISCMService,
|
||||
@ISCMViewService scmViewService: ISCMViewService,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@ICommandService commandService: ICommandService,
|
||||
@IEditorService editorService: IEditorService,
|
||||
@IInstantiationService _instantiationService: IInstantiationService,
|
||||
@IViewDescriptorService viewDescriptorService: IViewDescriptorService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IMenuService protected menuService: IMenuService,
|
||||
@IStorageService private storageService: IStorageService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService _contextKeyService: IContextKeyService,
|
||||
@IMenuService menuService: IMenuService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IOpenerService openerService: IOpenerService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
) {
|
||||
super(options, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
this._register(Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository)(() => this._onDidChangeViewWelcomeState.fire()));
|
||||
const contextKeyService = _contextKeyService.createScoped();
|
||||
const services = new ServiceCollection([IContextKeyService, contextKeyService]);
|
||||
const instantiationService = _instantiationService.createChild(services);
|
||||
|
||||
this._register(this.scmViewService.menus.titleMenu.onDidChangeTitle(this.updateActions, this));
|
||||
super({ ...options, titleMenuId: MenuId.SCMTitle }, keybindingService, contextMenuService, configurationService, contextKeyService, viewDescriptorService, instantiationService, openerService, themeService, telemetryService);
|
||||
|
||||
this.scmService = scmService;
|
||||
this.scmViewService = scmViewService;
|
||||
this.storageService = storageService;
|
||||
this.commandService = commandService;
|
||||
this.editorService = editorService;
|
||||
this.menuService = menuService;
|
||||
|
||||
this._onDidLayout = new Emitter<void>();
|
||||
this.layoutCache = {
|
||||
height: undefined,
|
||||
width: undefined,
|
||||
onDidChange: this._onDidLayout.event
|
||||
};
|
||||
|
||||
this._register(Event.any(this.scmService.onDidAddRepository, this.scmService.onDidRemoveRepository)(() => this._onDidChangeViewWelcomeState.fire()));
|
||||
// this._register(this.scmViewService.menus.titleMenu.onDidChangeTitle(this.updateActions, this));
|
||||
}
|
||||
|
||||
protected renderBody(container: HTMLElement): void {
|
||||
|
@ -1719,13 +1839,9 @@ export class SCMViewPane extends ViewPane {
|
|||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.providerCountBadge'))(updateProviderCountVisibility));
|
||||
updateProviderCountVisibility();
|
||||
|
||||
this._register(this.scmViewService.onDidChangeVisibleRepositories(() => this.updateActions()));
|
||||
|
||||
this.inputRenderer = this.instantiationService.createInstance(InputRenderer, this.layoutCache, overflowWidgetsDomNode, (input, height) => this.tree.updateElementHeight(input, height));
|
||||
const delegate = new ListDelegate(this.inputRenderer);
|
||||
|
||||
const actionViewItemProvider = (action: IAction) => this.getActionViewItem(action);
|
||||
|
||||
this.listLabels = this.instantiationService.createInstance(ResourceLabels, { onDidChangeVisibility: this.onDidChangeBodyVisibility });
|
||||
this._register(this.listLabels);
|
||||
|
||||
|
@ -1734,15 +1850,15 @@ export class SCMViewPane extends ViewPane {
|
|||
this._register(actionRunner.onBeforeRun(() => this.tree.domFocus()));
|
||||
|
||||
const renderers: ICompressibleTreeRenderer<any, any, any>[] = [
|
||||
this.instantiationService.createInstance(RepositoryRenderer, actionViewItemProvider),
|
||||
this.instantiationService.createInstance(RepositoryRenderer, getStatusBarActionViewItem),
|
||||
this.inputRenderer,
|
||||
this.instantiationService.createInstance(ResourceGroupRenderer, actionViewItemProvider),
|
||||
this.instantiationService.createInstance(ResourceRenderer, () => this.viewModel, this.listLabels, actionViewItemProvider, actionRunner)
|
||||
this.instantiationService.createInstance(ResourceGroupRenderer, getStatusBarActionViewItem),
|
||||
this.instantiationService.createInstance(ResourceRenderer, () => this._viewModel, this.listLabels, getStatusBarActionViewItem, actionRunner)
|
||||
];
|
||||
|
||||
const filter = new SCMTreeFilter();
|
||||
const sorter = new SCMTreeSorter(() => this.viewModel);
|
||||
const keyboardNavigationLabelProvider = this.instantiationService.createInstance(SCMTreeKeyboardNavigationLabelProvider, () => this.viewModel);
|
||||
const sorter = new SCMTreeSorter(() => this._viewModel);
|
||||
const keyboardNavigationLabelProvider = this.instantiationService.createInstance(SCMTreeKeyboardNavigationLabelProvider, () => this._viewModel);
|
||||
const identityProvider = new SCMResourceIdentityProvider();
|
||||
|
||||
this.tree = this.instantiationService.createInstance(
|
||||
|
@ -1789,41 +1905,40 @@ export class SCMViewPane extends ViewPane {
|
|||
} catch {/* noop */ }
|
||||
}
|
||||
|
||||
this.viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.inputRenderer, viewMode, ViewModelSortKey.Path, viewState);
|
||||
this._register(this.viewModel);
|
||||
this._register(this.instantiationService.createInstance(RepositoryVisibilityActionController));
|
||||
|
||||
this._viewModel = this.instantiationService.createInstance(ViewModel, this.tree, this.inputRenderer, viewMode, viewState);
|
||||
this._register(this._viewModel);
|
||||
|
||||
this.listContainer.classList.add('file-icon-themable-tree');
|
||||
this.listContainer.classList.add('show-file-icons');
|
||||
|
||||
this.updateIndentStyles(this.themeService.getFileIconTheme());
|
||||
this._register(this.themeService.onDidFileIconThemeChange(this.updateIndentStyles, this));
|
||||
this._register(this.viewModel.onDidChangeMode(this.onDidChangeMode, this));
|
||||
this._register(this._viewModel.onDidChangeMode(this.onDidChangeMode, this));
|
||||
|
||||
this.toggleViewModelModeAction = new ToggleViewModeAction(ToggleViewModeAction.ID, ToggleViewModeAction.LABEL, this.viewModel);
|
||||
this._register(this.toggleViewModelModeAction);
|
||||
|
||||
this._register(this.onDidChangeBodyVisibility(this.viewModel.setVisible, this.viewModel));
|
||||
this._register(this.onDidChangeBodyVisibility(this._viewModel.setVisible, this._viewModel));
|
||||
|
||||
this._register(Event.filter(this.configurationService.onDidChangeConfiguration, e => e.affectsConfiguration('scm.alwaysShowRepositories'))(this.updateActions, this));
|
||||
this.updateActions();
|
||||
|
||||
this._register(this.storageService.onWillSaveState(e => {
|
||||
if (e.reason === WillSaveStateReason.SHUTDOWN) {
|
||||
this.storageService.store(`scm.viewState`, JSON.stringify(this.viewModel.treeViewState), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
this.storageService.store(`scm.viewState`, JSON.stringify(this._viewModel.treeViewState), StorageScope.WORKSPACE, StorageTarget.MACHINE);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private updateIndentStyles(theme: IFileIconTheme): void {
|
||||
this.listContainer.classList.toggle('list-view-mode', this.viewModel.mode === ViewModelMode.List);
|
||||
this.listContainer.classList.toggle('tree-view-mode', this.viewModel.mode === ViewModelMode.Tree);
|
||||
this.listContainer.classList.toggle('align-icons-and-twisties', (this.viewModel.mode === ViewModelMode.List && theme.hasFileIcons) || (theme.hasFileIcons && !theme.hasFolderIcons));
|
||||
this.listContainer.classList.toggle('hide-arrows', this.viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true);
|
||||
this.listContainer.classList.toggle('list-view-mode', this._viewModel.mode === ViewModelMode.List);
|
||||
this.listContainer.classList.toggle('tree-view-mode', this._viewModel.mode === ViewModelMode.Tree);
|
||||
this.listContainer.classList.toggle('align-icons-and-twisties', (this._viewModel.mode === ViewModelMode.List && theme.hasFileIcons) || (theme.hasFileIcons && !theme.hasFolderIcons));
|
||||
this.listContainer.classList.toggle('hide-arrows', this._viewModel.mode === ViewModelMode.Tree && theme.hidesExplorerArrows === true);
|
||||
}
|
||||
|
||||
private onDidChangeMode(): void {
|
||||
this.updateIndentStyles(this.themeService.getFileIconTheme());
|
||||
this.storageService.store(`scm.viewMode`, this.viewModel.mode, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
this.storageService.store(`scm.viewMode`, this._viewModel.mode, StorageScope.WORKSPACE, StorageTarget.USER);
|
||||
}
|
||||
|
||||
layoutBody(height: number | undefined = this.layoutCache.height, width: number | undefined = this.layoutCache.width): void {
|
||||
|
@ -1847,56 +1962,10 @@ export class SCMViewPane extends ViewPane {
|
|||
super.focus();
|
||||
|
||||
if (this.isExpanded()) {
|
||||
this.viewModel.focus();
|
||||
this._viewModel.focus();
|
||||
}
|
||||
}
|
||||
|
||||
getActions(): IAction[] {
|
||||
const result = [];
|
||||
|
||||
if (this.toggleViewModelModeAction) {
|
||||
result.push(this.toggleViewModelModeAction);
|
||||
}
|
||||
|
||||
if (!this.viewModel) {
|
||||
return result;
|
||||
}
|
||||
|
||||
if (this.scmViewService.visibleRepositories.length < 2) {
|
||||
return [...result, ...this.viewModel.getViewActions()];
|
||||
}
|
||||
|
||||
return [
|
||||
...result,
|
||||
new SCMCollapseAction(this.viewModel),
|
||||
...this.viewModel.getViewActions()
|
||||
];
|
||||
}
|
||||
|
||||
getSecondaryActions(): IAction[] {
|
||||
if (!this.viewModel) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.viewModel.getViewSecondaryActions();
|
||||
}
|
||||
|
||||
getActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (action instanceof StatusBarAction) {
|
||||
return new StatusBarActionViewItem(action);
|
||||
}
|
||||
|
||||
return super.getActionViewItem(action);
|
||||
}
|
||||
|
||||
getActionsContext(): any {
|
||||
if (!this.viewModel) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.viewModel.getViewActionsContext();
|
||||
}
|
||||
|
||||
private async open(e: IOpenEvent<TreeElement | undefined>): Promise<void> {
|
||||
if (!e.element) {
|
||||
return;
|
||||
|
@ -1960,9 +2029,17 @@ export class SCMViewPane extends ViewPane {
|
|||
|
||||
private onListContextMenu(e: ITreeContextMenuEvent<TreeElement | null>): void {
|
||||
if (!e.element) {
|
||||
const menu = this.menuService.createMenu(Menus.ViewSort, this.contextKeyService);
|
||||
const actions: IAction[] = [];
|
||||
const disposable = createAndFillInContextMenuActions(menu, undefined, actions);
|
||||
|
||||
return this.contextMenuService.showContextMenu({
|
||||
getAnchor: () => e.anchor,
|
||||
getActions: () => getRepositoryVisibilityActions(this.scmService, this.scmViewService)
|
||||
getActions: () => actions,
|
||||
onHide: () => {
|
||||
disposable.dispose();
|
||||
menu.dispose();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -17,7 +17,6 @@ import { IExtensionService } from 'vs/workbench/services/extensions/common/exten
|
|||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IViewDescriptorService } from 'vs/workbench/common/views';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { SCMViewPane } from 'vs/workbench/contrib/scm/browser/scmViewPane';
|
||||
|
||||
export class SCMViewPaneContainer extends ViewPaneContainer {
|
||||
|
||||
|
@ -41,18 +40,6 @@ export class SCMViewPaneContainer extends ViewPaneContainer {
|
|||
parent.classList.add('scm-viewlet');
|
||||
}
|
||||
|
||||
getActionsContext(): unknown {
|
||||
if (this.views.length === 1) {
|
||||
const view = this.views[0];
|
||||
|
||||
if (view instanceof SCMViewPane) {
|
||||
return view.getActionsContext();
|
||||
}
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
|
||||
getOptimalWidth(): number {
|
||||
return 400;
|
||||
}
|
||||
|
|
|
@ -3,19 +3,17 @@
|
|||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput, ISCMService, ISCMViewService } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { ISCMResource, ISCMRepository, ISCMResourceGroup, ISCMInput } from 'vs/workbench/contrib/scm/common/scm';
|
||||
import { IMenu } from 'vs/platform/actions/common/actions';
|
||||
import { ActionBar } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { IDisposable, Disposable, combinedDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Action, IAction } from 'vs/base/common/actions';
|
||||
import { Action, IAction, IActionViewItem } from 'vs/base/common/actions';
|
||||
import { createAndFillInActionBarActions, createAndFillInContextMenuActions } from 'vs/platform/actions/browser/menuEntryActionViewItem';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
|
||||
import { renderLabelWithIcons } from 'vs/base/browser/ui/iconLabel/iconLabels';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
import { basename } from 'vs/base/common/resources';
|
||||
import { Iterable } from 'vs/base/common/iterator';
|
||||
import { reset } from 'vs/base/browser/dom';
|
||||
|
||||
export function isSCMRepository(element: any): element is ISCMRepository {
|
||||
|
@ -96,7 +94,7 @@ export class StatusBarAction extends Action {
|
|||
}
|
||||
}
|
||||
|
||||
export class StatusBarActionViewItem extends ActionViewItem {
|
||||
class StatusBarActionViewItem extends ActionViewItem {
|
||||
|
||||
constructor(action: StatusBarAction) {
|
||||
super(null, action, {});
|
||||
|
@ -109,25 +107,10 @@ export class StatusBarActionViewItem extends ActionViewItem {
|
|||
}
|
||||
}
|
||||
|
||||
export function getRepositoryVisibilityActions(scmService: ISCMService, scmViewService: ISCMViewService): IAction[] {
|
||||
const visible = new Set<IAction>();
|
||||
const actions = scmService.repositories.map(repository => {
|
||||
const label = repository.provider.rootUri ? basename(repository.provider.rootUri) : repository.provider.label;
|
||||
const action = new Action('scm.repository.toggleVisibility', label, undefined, true, async () => {
|
||||
scmViewService.toggleVisibility(repository);
|
||||
});
|
||||
|
||||
if (scmViewService.isVisible(repository)) {
|
||||
action.checked = true;
|
||||
visible.add(action);
|
||||
}
|
||||
|
||||
return action;
|
||||
});
|
||||
|
||||
if (visible.size === 1) {
|
||||
Iterable.first(visible.values())!.enabled = false;
|
||||
export function getStatusBarActionViewItem(action: IAction): IActionViewItem | undefined {
|
||||
if (action instanceof StatusBarAction) {
|
||||
return new StatusBarActionViewItem(action);
|
||||
}
|
||||
|
||||
return actions;
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
@ -145,7 +145,6 @@ export interface ISCMRepositoryMenus {
|
|||
}
|
||||
|
||||
export interface ISCMMenus {
|
||||
readonly titleMenu: ISCMTitleMenu;
|
||||
getRepositoryMenus(provider: ISCMProvider): ISCMRepositoryMenus;
|
||||
}
|
||||
|
||||
|
|
|
@ -570,7 +570,8 @@ export class SimpleFileDialog {
|
|||
return UpdateResult.InvalidPath;
|
||||
} else {
|
||||
const inputUriDirname = resources.dirname(valueUri);
|
||||
if (!resources.extUriIgnorePathCase.isEqual(resources.removeTrailingPathSeparator(this.currentFolder), inputUriDirname)) {
|
||||
if (!resources.extUriIgnorePathCase.isEqual(resources.removeTrailingPathSeparator(this.currentFolder), inputUriDirname)
|
||||
&& (!/^[a-zA-Z]:$/.test(this.filePickBox.value) || !equalsIgnoreCase(this.pathFromUri(this.currentFolder).substring(0, this.filePickBox.value.length), this.filePickBox.value))) {
|
||||
let statWithoutTrailing: IFileStat | undefined;
|
||||
try {
|
||||
statWithoutTrailing = await this.fileService.resolve(inputUriDirname);
|
||||
|
|
|
@ -38,7 +38,7 @@ import { ColorScheme } from 'vs/platform/theme/common/theme';
|
|||
import { IHostColorSchemeService } from 'vs/workbench/services/themes/common/hostColorSchemeService';
|
||||
import { RunOnceScheduler, Sequencer } from 'vs/base/common/async';
|
||||
import { IUserDataInitializationService } from 'vs/workbench/services/userData/browser/userDataInit';
|
||||
import { getIconRegistry } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { getIconsStyleSheet } from 'vs/platform/theme/browser/iconsStyleSheet';
|
||||
|
||||
// implementation
|
||||
|
||||
|
@ -183,13 +183,13 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
|||
const codiconStyleSheet = createStyleSheet();
|
||||
codiconStyleSheet.id = 'codiconStyles';
|
||||
|
||||
const iconRegistry = getIconRegistry();
|
||||
const iconsStyleSheet = getIconsStyleSheet();
|
||||
function updateAll() {
|
||||
codiconStyleSheet.textContent = iconRegistry.getCSS();
|
||||
codiconStyleSheet.textContent = iconsStyleSheet.getCSS();
|
||||
}
|
||||
|
||||
const delayer = new RunOnceScheduler(updateAll, 0);
|
||||
iconRegistry.onDidChange(() => delayer.schedule());
|
||||
iconsStyleSheet.onDidChange(() => delayer.schedule());
|
||||
delayer.schedule();
|
||||
}
|
||||
|
||||
|
|
234
src/vs/workbench/services/themes/common/iconExtensionPoint.ts
Normal file
234
src/vs/workbench/services/themes/common/iconExtensionPoint.ts
Normal file
|
@ -0,0 +1,234 @@
|
|||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the MIT License. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { ExtensionsRegistry } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { IIconRegistry, Extensions as IconRegistryExtensions, IconFontDefinition } from 'vs/platform/theme/common/iconRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { CSSIcon } from 'vs/base/common/codicons';
|
||||
import { fontIdRegex } from 'vs/workbench/services/themes/common/productIconThemeSchema';
|
||||
import * as resources from 'vs/base/common/resources';
|
||||
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
|
||||
interface IIconExtensionPoint {
|
||||
id: string;
|
||||
description: string;
|
||||
default: { iconFontId: string; character: string; } | string;
|
||||
}
|
||||
|
||||
interface IIconFontExtensionPoint {
|
||||
id: string;
|
||||
src: {
|
||||
path: string;
|
||||
format: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
const iconRegistry: IIconRegistry = Registry.as<IIconRegistry>(IconRegistryExtensions.IconContribution);
|
||||
|
||||
const iconReferenceSchema = iconRegistry.getIconReferenceSchema();
|
||||
const iconIdPattern = `^${CSSIcon.iconNameExpression}$`;
|
||||
|
||||
const iconConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IIconExtensionPoint[]>({
|
||||
extensionPoint: 'icons',
|
||||
jsonSchema: {
|
||||
description: nls.localize('contributes.icons', 'Contributes extension defined themable icons'),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.icon.id', 'The identifier of the themable icon'),
|
||||
pattern: iconIdPattern,
|
||||
patternErrorMessage: nls.localize('contributes.icon.id.format', 'Identifiers must only contain letters, digits and minus.'),
|
||||
},
|
||||
description: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.icon.description', 'The description of the themable icon'),
|
||||
},
|
||||
default: {
|
||||
anyOf: [
|
||||
iconReferenceSchema,
|
||||
{
|
||||
type: 'object',
|
||||
properties: {
|
||||
iconFontId: {
|
||||
description: nls.localize('contributes.icon.default.iconFontId', 'The id of the icon font that defines the icon.'),
|
||||
type: 'string'
|
||||
},
|
||||
character: {
|
||||
description: nls.localize('contributes.icon.default.character', 'The character for the icon in the icon font.'),
|
||||
type: 'string'
|
||||
}
|
||||
},
|
||||
defaultSnippets: [{ body: { iconFontId: '${1:myIconFont}', character: '${2:\\\\E001}' } }]
|
||||
}
|
||||
],
|
||||
description: nls.localize('contributes.icon.default', 'The default of the icon. Either a reference to an extisting ThemeIcon or an icon in an icon font.'),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const iconFontConfigurationExtPoint = ExtensionsRegistry.registerExtensionPoint<IIconFontExtensionPoint[]>({
|
||||
extensionPoint: 'iconFonts',
|
||||
jsonSchema: {
|
||||
description: nls.localize('contributes.iconFonts', 'Contributes icon fonts to be used by icon contributions.'),
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
id: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.iconFonts.id', 'The ID of the font.'),
|
||||
pattern: fontIdRegex,
|
||||
patternErrorMessage: nls.localize('contributes.iconFonts.id.formatError', 'The ID must only contain letters, numbers, underscore and minus.')
|
||||
},
|
||||
src: {
|
||||
type: 'array',
|
||||
description: nls.localize('contributes.iconFonts.src', 'The location of the font.'),
|
||||
items: {
|
||||
type: 'object',
|
||||
properties: {
|
||||
path: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.iconFonts.src.path', 'The font path, relative to the current extension location.'),
|
||||
},
|
||||
format: {
|
||||
type: 'string',
|
||||
description: nls.localize('contributes.iconFonts.src.format', 'The format of the font.'),
|
||||
enum: ['woff', 'woff2', 'truetype', 'opentype', 'embedded-opentype', 'svg']
|
||||
}
|
||||
},
|
||||
required: [
|
||||
'path',
|
||||
'format'
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
export class IconExtensionPoint {
|
||||
|
||||
constructor() {
|
||||
iconConfigurationExtPoint.setHandler((extensions, delta) => {
|
||||
for (const extension of delta.added) {
|
||||
const extensionValue = <IIconExtensionPoint[]>extension.value;
|
||||
const collector = extension.collector;
|
||||
|
||||
if (!extension.description.enableProposedApi) {
|
||||
collector.error(nls.localize('invalid.icons.proposedAPI', "'configuration.icons is a proposed contribution point and only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!extensionValue || !Array.isArray(extensionValue)) {
|
||||
collector.error(nls.localize('invalid.icons.configuration', "'configuration.icons' must be a array"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const iconContribution of extensionValue) {
|
||||
if (typeof iconContribution.id !== 'string' || iconContribution.id.length === 0) {
|
||||
collector.error(nls.localize('invalid.icons.id', "'configuration.icons.id' must be defined and can not be empty"));
|
||||
return;
|
||||
}
|
||||
if (!iconContribution.id.match(iconIdPattern)) {
|
||||
collector.error(nls.localize('invalid.icons.id.format', "'configuration.icons.id' must only contain letters, digits and minuses"));
|
||||
return;
|
||||
}
|
||||
if (typeof iconContribution.description !== 'string' || iconContribution.id.length === 0) {
|
||||
collector.error(nls.localize('invalid.icons.description', "'configuration.icons.description' must be defined and can not be empty"));
|
||||
return;
|
||||
}
|
||||
let defaultIcon = iconContribution.default;
|
||||
if (typeof defaultIcon === 'string') {
|
||||
iconRegistry.registerIcon(iconContribution.id, { id: defaultIcon }, iconContribution.description);
|
||||
} else if (typeof defaultIcon === 'object' && typeof defaultIcon.iconFontId === 'string' && typeof defaultIcon.character === 'string') {
|
||||
iconRegistry.registerIcon(iconContribution.id, {
|
||||
fontId: getFontId(extension.description, defaultIcon.iconFontId),
|
||||
character: defaultIcon.character,
|
||||
}, iconContribution.description);
|
||||
} else {
|
||||
collector.error(nls.localize('invalid.icons.default', "'configuration.icons.default' must be either a reference to the id of an other theme icon (string) or a icon definition (object)"));
|
||||
}
|
||||
}
|
||||
}
|
||||
for (const extension of delta.removed) {
|
||||
const extensionValue = <IIconExtensionPoint[]>extension.value;
|
||||
for (const iconContribution of extensionValue) {
|
||||
iconRegistry.deregisterIcon(iconContribution.id);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class IconFontExtensionPoint {
|
||||
|
||||
constructor() {
|
||||
iconFontConfigurationExtPoint.setHandler((_extensions, delta) => {
|
||||
for (const extension of delta.added) {
|
||||
const extensionValue = <IIconFontExtensionPoint[]>extension.value;
|
||||
const collector = extension.collector;
|
||||
|
||||
if (!extension.description.enableProposedApi) {
|
||||
collector.error(nls.localize('invalid.iconFonts.proposedAPI', "'configuration.iconFonts is a proposed contribution point and only available when running out of dev or with the following command line switch: --enable-proposed-api {0}", extension.description.identifier.value));
|
||||
return;
|
||||
}
|
||||
|
||||
if (!extensionValue || !Array.isArray(extensionValue)) {
|
||||
collector.error(nls.localize('invalid.iconFonts.configuration', "'configuration.iconFonts' must be a array"));
|
||||
return;
|
||||
}
|
||||
|
||||
for (const iconFontContribution of extensionValue) {
|
||||
if (typeof iconFontContribution.id !== 'string' || iconFontContribution.id.length === 0) {
|
||||
collector.error(nls.localize('invalid.iconFonts.id', "'configuration.iconFonts.id' must be defined and can not be empty"));
|
||||
return;
|
||||
}
|
||||
if (!iconFontContribution.id.match(fontIdRegex)) {
|
||||
collector.error(nls.localize('invalid.iconFonts.id.format', "'configuration.iconFonts.id' must only contain letters, numbers, underscore and minus."));
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(iconFontContribution.src) || !iconFontContribution.src.length) {
|
||||
collector.error(nls.localize('invalid.iconFonts.src', "'configuration.iconFonts.src' must be an array with locations of the icon font."));
|
||||
return;
|
||||
}
|
||||
const def: IconFontDefinition = { src: [] };
|
||||
for (const src of iconFontContribution.src) {
|
||||
if (typeof src === 'object' && typeof src.path === 'string' && typeof src.format === 'string') {
|
||||
const extensionLocation = extension.description.extensionLocation;
|
||||
const iconFontLocation = resources.joinPath(extensionLocation, src.path);
|
||||
if (!resources.isEqualOrParent(iconFontLocation, extensionLocation)) {
|
||||
collector.warn(nls.localize('invalid.iconFonts.src.path', "Expected `contributes.iconFonts.src.path` ({0}) to be included inside extension's folder ({0}). This might make the extension non-portable.", iconFontLocation.path, extensionLocation.path));
|
||||
}
|
||||
def.src.push({
|
||||
location: iconFontLocation,
|
||||
format: src.format,
|
||||
});
|
||||
} else {
|
||||
collector.error(nls.localize('invalid.iconFonts.src.item', "Items of 'configuration.iconFonts.src' must be objects with properties 'path' and 'format'"));
|
||||
}
|
||||
}
|
||||
iconRegistry.registerIconFont(getFontId(extension.description, iconFontContribution.id), def);
|
||||
}
|
||||
}
|
||||
for (const extension of delta.removed) {
|
||||
const extensionValue = <IIconFontExtensionPoint[]>extension.value;
|
||||
for (const iconFontContribution of extensionValue) {
|
||||
iconRegistry.deregisterIconFont(getFontId(extension.description, iconFontContribution.id));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function getFontId(description: IExtensionDescription, iconFontId: string) {
|
||||
return `${description.identifier.value}/${iconFontId}`;
|
||||
}
|
Loading…
Reference in a new issue