未验证 提交 4cb5bb65 编写于 作者: M Martin Aeschlimann 提交者: GitHub

Merge branch 'master' into aeschli/preferencesIcons

......@@ -11,7 +11,7 @@ import { Color } from 'vs/base/common/color';
import { Emitter, Event } from 'vs/base/common/event';
import { dispose, IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
import { ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
import 'vs/css!./breadcrumbsWidget';
export abstract class BreadcrumbsItem {
......@@ -56,7 +56,7 @@ export interface IBreadcrumbsItemEvent {
payload: any;
}
const breadcrumbSeparatorIcon = registerIcon('breadcrumb-separator', Codicon.chevronRight);
const breadcrumbSeparatorIcon = registerCodicon('breadcrumb-separator', Codicon.chevronRight);
export class BreadcrumbsWidget {
......
......@@ -17,7 +17,7 @@ import { Action } from 'vs/base/common/actions';
import { mnemonicButtonLabel } from 'vs/base/common/labels';
import { isMacintosh, isLinux } from 'vs/base/common/platform';
import { SimpleCheckbox, ISimpleCheckboxStyles } from 'vs/base/browser/ui/checkbox/checkbox';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
import { InputBox } from 'vs/base/browser/ui/inputbox/inputBox';
export interface IDialogInputOptions {
......@@ -60,10 +60,10 @@ interface ButtonMapEntry {
readonly index: number;
}
const dialogErrorIcon = registerIcon('dialog-error', Codicon.error);
const dialogWarningIcon = registerIcon('dialog-warning', Codicon.warning);
const dialogInfoIcon = registerIcon('dialog-info', Codicon.info);
const dialogCloseIcon = registerIcon('dialog-close', Codicon.close);
const dialogErrorIcon = registerCodicon('dialog-error', Codicon.error);
const dialogWarningIcon = registerCodicon('dialog-warning', Codicon.warning);
const dialogInfoIcon = registerCodicon('dialog-info', Codicon.info);
const dialogCloseIcon = registerCodicon('dialog-close', Codicon.close);
export class Dialog extends Disposable {
private readonly element: HTMLElement;
......
......@@ -18,7 +18,7 @@ import { ScrollbarVisibility, ScrollEvent } from 'vs/base/common/scrollable';
import { Event } from 'vs/base/common/event';
import { AnchorAlignment, layout, LayoutAnchorPosition } from 'vs/base/browser/ui/contextview/contextview';
import { isLinux, isMacintosh } from 'vs/base/common/platform';
import { Codicon, registerIcon, stripCodicons } from 'vs/base/common/codicons';
import { Codicon, registerCodicon, stripCodicons } from 'vs/base/common/codicons';
import { BaseActionViewItem, ActionViewItem, IActionViewItemOptions } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { formatRule } from 'vs/base/browser/ui/codicons/codiconStyles';
import { isFirefox } from 'vs/base/browser/browser';
......@@ -27,8 +27,8 @@ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
export const MENU_MNEMONIC_REGEX = /\(&([^\s&])\)|(^|[^&])&([^\s&])/;
export const MENU_ESCAPED_MNEMONIC_REGEX = /(&)?(&)([^\s&])/g;
const menuSelectionIcon = registerIcon('menu-selection', Codicon.check);
const menuSubmenuIcon = registerIcon('menu-submenu', Codicon.chevronRight);
const menuSelectionIcon = registerCodicon('menu-selection', Codicon.check);
const menuSubmenuIcon = registerCodicon('menu-submenu', Codicon.chevronRight);
export enum Direction {
Right,
......
......@@ -21,11 +21,11 @@ import { asArray } from 'vs/base/common/arrays';
import { ScanCodeUtils, ScanCode } from 'vs/base/common/scanCode';
import { isMacintosh } from 'vs/base/common/platform';
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
const $ = DOM.$;
const menuBarMoreIcon = registerIcon('menubar-more', Codicon.more);
const menuBarMoreIcon = registerCodicon('menubar-more', Codicon.more);
export interface IMenuBarOptions {
enableMnemonics?: boolean;
......
......@@ -9,11 +9,11 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
const scrollbarButtonLeftIcon = registerIcon('scrollbar-button-left', Codicon.triangleLeft);
const scrollbarButtonRightIcon = registerIcon('scrollbar-button-right', Codicon.triangleRight);
const scrollbarButtonLeftIcon = registerCodicon('scrollbar-button-left', Codicon.triangleLeft);
const scrollbarButtonRightIcon = registerCodicon('scrollbar-button-right', Codicon.triangleRight);
export class HorizontalScrollbar extends AbstractScrollbar {
......
......@@ -9,10 +9,10 @@ import { ScrollableElementResolvedOptions } from 'vs/base/browser/ui/scrollbar/s
import { ARROW_IMG_SIZE } from 'vs/base/browser/ui/scrollbar/scrollbarArrow';
import { ScrollbarState } from 'vs/base/browser/ui/scrollbar/scrollbarState';
import { INewScrollPosition, ScrollEvent, Scrollable, ScrollbarVisibility } from 'vs/base/common/scrollable';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
const scrollbarButtonUpIcon = registerIcon('scrollbar-button-up', Codicon.triangleUp);
const scrollbarButtonDownIcon = registerIcon('scrollbar-button-down', Codicon.triangleDown);
const scrollbarButtonUpIcon = registerCodicon('scrollbar-button-up', Codicon.triangleUp);
const scrollbarButtonDownIcon = registerCodicon('scrollbar-button-down', Codicon.triangleDown);
export class VerticalScrollbar extends AbstractScrollbar {
......
......@@ -11,12 +11,12 @@ import { ResolvedKeybinding } from 'vs/base/common/keyCodes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { AnchorAlignment } from 'vs/base/browser/ui/contextview/contextview';
import { withNullAsUndefined } from 'vs/base/common/types';
import { Codicon, CSSIcon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, CSSIcon, registerCodicon } from 'vs/base/common/codicons';
import { EventMultiplexer } from 'vs/base/common/event';
import { DropdownMenuActionViewItem } from 'vs/base/browser/ui/dropdown/dropdownActionViewItem';
import { IContextMenuProvider } from 'vs/base/browser/contextmenu';
const toolBarMoreIcon = registerIcon('toolbar-more', Codicon.more);
const toolBarMoreIcon = registerCodicon('toolbar-more', Codicon.more);
export interface IToolBarOptions {
orientation?: ActionsOrientation;
......
......@@ -3,12 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon, registerCodicon } from 'vs/base/common/codicons';
export const treeItemExpandedIcon = registerIcon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation
export const treeItemExpandedIcon = registerCodicon('tree-item-expanded', Codicon.chevronDown); // collapsed is done with rotation
export const treeFilterOnTypeOnIcon = registerIcon('tree-filter-on-type-on', Codicon.listFilter);
export const treeFilterOnTypeOffIcon = registerIcon('tree-filter-on-type-off', Codicon.listSelection);
export const treeFilterClearIcon = registerIcon('tree-filter-clear', Codicon.close);
export const treeFilterOnTypeOnIcon = registerCodicon('tree-filter-on-type-on', Codicon.listFilter);
export const treeFilterOnTypeOffIcon = registerCodicon('tree-filter-on-type-off', Codicon.listSelection);
export const treeFilterClearIcon = registerCodicon('tree-filter-clear', Codicon.close);
export const treeItemLoadingIcon = registerIcon('tree-item-loading', Codicon.loading);
export const treeItemLoadingIcon = registerCodicon('tree-item-loading', Codicon.loading);
......@@ -47,7 +47,7 @@ const _registry = new Registry();
export const iconRegistry: IIconRegistry = _registry;
export function registerIcon(id: string, def: Codicon, description?: string) {
export function registerCodicon(id: string, def: Codicon, description?: string): Codicon {
return new Codicon(id, def, description);
}
......
......@@ -27,7 +27,7 @@ import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/lis
import { List, IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget';
import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
import { Color } from 'vs/base/common/color';
import { registerIcon, Codicon } from 'vs/base/common/codicons';
import { registerCodicon, Codicon } from 'vs/base/common/codicons';
import { ActionViewItem } from 'vs/base/browser/ui/actionbar/actionViewItems';
import { escape } from 'vs/base/common/strings';
import { renderCodicons } from 'vs/base/browser/codicons';
......@@ -72,7 +72,7 @@ const $ = dom.$;
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
const backButtonIcon = registerIcon('quick-input-back', Codicon.arrowLeft, localize('backButtonIcon', 'Icon for the back button in the quick input dialog.'));
const backButtonIcon = registerCodicon('quick-input-back', Codicon.arrowLeft, localize('backButtonIcon', 'Icon for the back button in the quick input dialog.'));
const backButton = {
iconClass: backButtonIcon.classNames,
......
......@@ -331,7 +331,7 @@ export class Main {
return;
}
console.log(localize('uninstalling', "Uninstalling {0}...", id));
await this.extensionManagementService.uninstall(extensionToUninstall, true);
await this.extensionManagementService.uninstall(extensionToUninstall);
uninstalledExtensions.push(extensionToUninstall);
console.log(localize('successUninstall', "Extension '{0}' was successfully uninstalled!", id));
}
......
......@@ -201,7 +201,8 @@ export class ExtensionManagementError extends Error {
}
}
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean };
export type InstallOptions = { isBuiltin?: boolean, isMachineScoped?: boolean, donotIncludePackAndDependencies?: boolean };
export type UninstallOptions = { donotIncludePack?: boolean, donotCheckDependents?: boolean };
export const IExtensionManagementService = createDecorator<IExtensionManagementService>('extensionManagementService');
export interface IExtensionManagementService {
......@@ -218,7 +219,7 @@ export interface IExtensionManagementService {
install(vsix: URI, options?: InstallOptions): Promise<ILocalExtension>;
canInstall(extension: IGalleryExtension): Promise<boolean>;
installFromGallery(extension: IGalleryExtension, options?: InstallOptions): Promise<ILocalExtension>;
uninstall(extension: ILocalExtension, force?: boolean): Promise<void>;
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void>;
reinstallFromGallery(extension: ILocalExtension): Promise<void>;
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]>;
getExtensionsReport(): Promise<IReportedExtension[]>;
......
......@@ -4,7 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementService, ILocalExtension, InstallExtensionEvent, DidInstallExtensionEvent, IGalleryExtension, DidUninstallExtensionEvent, IExtensionIdentifier, IGalleryMetadata, IReportedExtension, IExtensionTipsService, InstallOptions, UninstallOptions } from 'vs/platform/extensionManagement/common/extensionManagement';
import { Emitter, Event } from 'vs/base/common/event';
import { URI, UriComponents } from 'vs/base/common/uri';
import { IURITransformer, DefaultURITransformer, transformAndReviveIncomingURIs } from 'vs/base/common/uriIpc';
......@@ -128,8 +128,8 @@ export class ExtensionManagementChannelClient extends Disposable implements IExt
return Promise.resolve(this.channel.call<ILocalExtension>('installFromGallery', [extension, installOptions])).then(local => transformIncomingExtension(local, null));
}
uninstall(extension: ILocalExtension, force = false): Promise<void> {
return Promise.resolve(this.channel.call('uninstall', [extension!, force]));
uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
return Promise.resolve(this.channel.call('uninstall', [extension!, options]));
}
reinstallFromGallery(extension: ILocalExtension): Promise<void> {
......
......@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable } from 'vs/base/common/lifecycle';
import { rename } from 'vs/base/node/pfs';
import { IFileService, IFileStatWithMetadata } from 'vs/platform/files/common/files';
import { IExtensionGalleryService, IGalleryExtension, InstallOperation } from 'vs/platform/extensionManagement/common/extensionManagement';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
......@@ -13,6 +14,7 @@ import { ExtensionIdentifierWithVersion, groupByExtension } from 'vs/platform/ex
import { ILogService } from 'vs/platform/log/common/log';
import { generateUuid } from 'vs/base/common/uuid';
import * as semver from 'vs/base/common/semver/semver';
import { isWindows } from 'vs/base/common/platform';
const ExtensionIdVersionRegex = /^([^.]+\..+)-(\d+\.\d+\.\d+)$/;
......@@ -36,8 +38,21 @@ export class ExtensionsDownloader extends Disposable {
async downloadExtension(extension: IGalleryExtension, operation: InstallOperation): Promise<URI> {
await this.cleanUpPromise;
const location = joinPath(this.extensionsDownloadDir, this.getName(extension));
await this.download(extension, location, operation);
const vsixName = this.getName(extension);
const location = joinPath(this.extensionsDownloadDir, vsixName);
// Download only if vsix does not exist
if (!await this.fileService.exists(location)) {
// Download to temporary location first only if vsix does not exist
const tempLocation = joinPath(this.extensionsDownloadDir, `.${vsixName}`);
if (!await this.fileService.exists(tempLocation)) {
await this.extensionGalleryService.download(extension, tempLocation, operation);
}
// Rename temp location to original
await this.rename(tempLocation, location, Date.now() + (2 * 60 * 1000) /* Retry for 2 minutes */);
}
return location;
}
......@@ -45,9 +60,15 @@ export class ExtensionsDownloader extends Disposable {
// noop as caching is enabled always
}
private async download(extension: IGalleryExtension, location: URI, operation: InstallOperation): Promise<void> {
if (!await this.fileService.exists(location)) {
await this.extensionGalleryService.download(extension, location, operation);
private async rename(from: URI, to: URI, retryUntil: number): Promise<void> {
try {
await rename(from.fsPath, to.fsPath);
} catch (error) {
if (isWindows && error && error.code === 'EPERM' && Date.now() < retryUntil) {
this.logService.info(`Failed renaming ${from} to ${to} with 'EPERM' error. Trying again...`);
return this.rename(from, to, retryUntil);
}
throw error;
}
}
......
......@@ -20,7 +20,8 @@ import {
INSTALL_ERROR_MALICIOUS,
INSTALL_ERROR_INCOMPATIBLE,
ExtensionManagementError,
InstallOptions
InstallOptions,
UninstallOptions
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { areSameExtensions, getGalleryExtensionId, getMaliciousExtensionsSet, getGalleryExtensionTelemetryData, getLocalExtensionTelemetryData, ExtensionIdentifierWithVersion } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { INativeEnvironmentService } from 'vs/platform/environment/common/environment';
......@@ -297,11 +298,13 @@ export class ExtensionManagementService extends Disposable implements IExtension
try { await this.extensionsDownloader.delete(URI.file(installableExtension.zipPath)); } catch (error) { /* Ignore */ }
try {
await this.installDependenciesAndPackExtensions(local, existingExtension, options);
} catch (error) {
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
throw error;
if (!options.donotIncludePackAndDependencies) {
try {
await this.installDependenciesAndPackExtensions(local, existingExtension, options);
} catch (error) {
try { await this.uninstall(local); } catch (error) { /* Ignore */ }
throw error;
}
}
if (existingExtension && semver.neq(existingExtension.manifest.version, extension.version)) {
......@@ -471,7 +474,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
await Promise.all(extensionsToUninstall.map(local => this.uninstall(local)));
}
async uninstall(extension: ILocalExtension): Promise<void> {
async uninstall(extension: ILocalExtension, options: UninstallOptions = {}): Promise<void> {
this.logService.trace('ExtensionManagementService#uninstall', extension.identifier.id);
const installed = await this.getInstalled(ExtensionType.User);
const extensionToUninstall = installed.find(e => areSameExtensions(e.identifier, extension.identifier));
......@@ -480,7 +483,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
}
try {
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed);
await this.checkForDependenciesAndUninstall(extensionToUninstall, installed, options);
} catch (error) {
throw this.joinErrors(error);
}
......@@ -533,15 +536,11 @@ export class ExtensionManagementService extends Disposable implements IExtension
}, new Error(''));
}
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[]): Promise<void> {
private async checkForDependenciesAndUninstall(extension: ILocalExtension, installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
try {
await this.preUninstallExtension(extension);
const packedExtensions = this.getAllPackExtensionsToUninstall(extension, installed);
if (packedExtensions.length) {
await this.uninstallExtensions(extension, packedExtensions, installed);
} else {
await this.uninstallExtensions(extension, [], installed);
}
const packedExtensions = options.donotIncludePack ? [] : this.getAllPackExtensionsToUninstall(extension, installed);
await this.uninstallExtensions(extension, packedExtensions, installed, options);
} catch (error) {
await this.postUninstallExtension(extension, new ExtensionManagementError(error instanceof Error ? error.message : error, INSTALL_ERROR_LOCAL));
throw error;
......@@ -549,10 +548,12 @@ export class ExtensionManagementService extends Disposable implements IExtension
await this.postUninstallExtension(extension);
}
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[]): Promise<void> {
private async uninstallExtensions(extension: ILocalExtension, otherExtensionsToUninstall: ILocalExtension[], installed: ILocalExtension[], options: UninstallOptions): Promise<void> {
const extensionsToUninstall = [extension, ...otherExtensionsToUninstall];
for (const e of extensionsToUninstall) {
this.checkForDependents(e, extensionsToUninstall, installed, extension);
if (!options.donotCheckDependents) {
for (const e of extensionsToUninstall) {
this.checkForDependents(e, extensionsToUninstall, installed, extension);
}
}
await Promise.all([this.uninstallExtension(extension), ...otherExtensionsToUninstall.map(d => this.doUninstall(d))]);
}
......
......@@ -259,3 +259,6 @@ iconRegistry.onDidChange(() => {
// common icons
export const widgetClose = registerIcon('widget-close', Codicons.Codicon.close, localize('widgetClose', 'Icon for the close action in widgets.'));
export const gotoPreviousLocation = registerIcon('goto-previous-location', Codicons.Codicon.arrowUp, localize('previousChangeIcon', 'Icon for goto previous editor location.'));
export const gotoNextLocation = registerIcon('goto-next-location', Codicons.Codicon.arrowDown, localize('nextChangeIcon', 'Icon for goto next editor location.'));
......@@ -350,7 +350,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const extensionsToRemove = installedExtensions.filter(({ identifier, isBuiltin }) => !isBuiltin && removed.some(r => areSameExtensions(identifier, r)));
await Promise.all(extensionsToRemove.map(async extensionToRemove => {
this.logService.trace(`${this.syncResourceLogLabel}: Uninstalling local extension...`, extensionToRemove.identifier.id);
await this.extensionManagementService.uninstall(extensionToRemove);
await this.extensionManagementService.uninstall(extensionToRemove, { donotIncludePack: true, donotCheckDependents: true });
this.logService.info(`${this.syncResourceLogLabel}: Uninstalled local extension.`, extensionToRemove.identifier.id);
removeFromSkipped.push(extensionToRemove.identifier);
}));
......@@ -407,7 +407,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
// Install only if the extension does not exist
if (!installedExtension) {
this.logService.trace(`${this.syncResourceLogLabel}: Installing extension...`, e.identifier.id, extension.version);
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false } /* pass options to prevent install and sync dialog in web */);
await this.extensionManagementService.installFromGallery(extension, { isMachineScoped: false, donotIncludePackAndDependencies: true } /* pass options to prevent install and sync dialog in web */);
this.logService.info(`${this.syncResourceLogLabel}: Installed extension.`, e.identifier.id, extension.version);
removeFromSkipped.push(extension.identifier);
}
......@@ -572,5 +572,3 @@ export class ExtensionsInitializer extends AbstractInitializer {
}
}
......@@ -51,7 +51,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { throwProposedApiError, checkProposedApiEnabled } from 'vs/workbench/services/extensions/common/extensions';
import { ProxyIdentifier } from 'vs/workbench/services/extensions/common/proxyIdentifier';
import { ExtensionDescriptionRegistry } from 'vs/workbench/services/extensions/common/extensionDescriptionRegistry';
import * as vscode from 'vscode';
import type * as vscode from 'vscode';
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
import { originalFSPath } from 'vs/base/common/resources';
import { values } from 'vs/base/common/collections';
......@@ -203,40 +203,50 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
})();
const authentication: typeof vscode.authentication = {
getSession(providerId: string, scopes: string[], options?: vscode.AuthenticationGetSessionOptions) {
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
},
get onDidChangeSessions(): Event<vscode.AuthenticationSessionsChangeEvent> {
return extHostAuthentication.onDidChangeSessions;
},
registerAuthenticationProvider(provider: vscode.AuthenticationProvider): vscode.Disposable {
checkProposedApiEnabled(extension);
return extHostAuthentication.registerAuthenticationProvider(provider);
},
get onDidChangeAuthenticationProviders(): Event<vscode.AuthenticationProvidersChangeEvent> {
checkProposedApiEnabled(extension);
return extHostAuthentication.onDidChangeAuthenticationProviders;
},
getProviderIds(): Thenable<ReadonlyArray<string>> {
checkProposedApiEnabled(extension);
return extHostAuthentication.getProviderIds();
},
get providerIds(): string[] {
checkProposedApiEnabled(extension);
return extHostAuthentication.providerIds;
},
get providers(): ReadonlyArray<vscode.AuthenticationProviderInformation> {
checkProposedApiEnabled(extension);
return extHostAuthentication.providers;
},
getSession(providerId: string, scopes: string[], options?: vscode.AuthenticationGetSessionOptions) {
return extHostAuthentication.getSession(extension, providerId, scopes, options as any);
},
logout(providerId: string, sessionId: string): Thenable<void> {
checkProposedApiEnabled(extension);
return extHostAuthentication.logout(providerId, sessionId);
},
get onDidChangeSessions(): Event<vscode.AuthenticationSessionsChangeEvent> {
return extHostAuthentication.onDidChangeSessions;
},
getPassword(key: string): Thenable<string | undefined> {
checkProposedApiEnabled(extension);
return extHostAuthentication.getPassword(extension, key);
},
setPassword(key: string, value: string): Thenable<void> {
checkProposedApiEnabled(extension);
return extHostAuthentication.setPassword(extension, key, value);
},
deletePassword(key: string): Thenable<void> {
checkProposedApiEnabled(extension);
return extHostAuthentication.deletePassword(extension, key);
},
get onDidChangePassword(): Event<void> {
checkProposedApiEnabled(extension);
return extHostAuthentication.onDidChangePassword;
}
};
......@@ -894,14 +904,13 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
}
};
const comment: typeof vscode.comments = {
// namespace: comments
const comments: typeof vscode.comments = {
createCommentController(id: string, label: string) {
return extHostComment.createCommentController(extension, id, label);
}
};
const comments = comment;
// namespace: debug
const debug: typeof vscode.debug = {
get activeDebugSession() {
......@@ -1038,6 +1047,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
return extHostNotebook.registerNotebookKernelProvider(extension, selector, provider);
},
createNotebookEditorDecorationType(options: vscode.NotebookDecorationRenderOptions): vscode.NotebookEditorDecorationType {
checkProposedApiEnabled(extension);
return extHostNotebook.createNotebookEditorDecorationType(options);
},
get activeNotebookEditor(): vscode.NotebookEditor | undefined {
......@@ -1091,7 +1101,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
// namespaces
authentication,
commands,
comment,
comments,
debug,
env,
......@@ -1112,7 +1121,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
CodeAction: extHostTypes.CodeAction,
CodeActionKind: extHostTypes.CodeActionKind,
CodeActionTrigger: extHostTypes.CodeActionTrigger,
CodeInset: extHostTypes.CodeInset,
CodeLens: extHostTypes.CodeLens,
Color: extHostTypes.Color,
ColorInformation: extHostTypes.ColorInformation,
......@@ -1149,7 +1157,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
EventEmitter: Emitter,
ExtensionKind: extHostTypes.ExtensionKind,
ExtensionMode: extHostTypes.ExtensionMode,
ExtensionRuntime: extHostTypes.ExtensionRuntime,
FileChangeType: extHostTypes.FileChangeType,
FileDecoration: extHostTypes.FileDecoration,
FileSystemError: extHostTypes.FileSystemError,
......@@ -1160,7 +1167,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
Hover: extHostTypes.Hover,
IndentAction: languageConfiguration.IndentAction,
Location: extHostTypes.Location,
LogLevel: extHostTypes.LogLevel,
MarkdownString: extHostTypes.MarkdownString,
OverviewRulerLane: OverviewRulerLane,
ParameterInformation: extHostTypes.ParameterInformation,
......@@ -1170,8 +1176,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
QuickInputButtons: extHostTypes.QuickInputButtons,
Range: extHostTypes.Range,
RelativePattern: extHostTypes.RelativePattern,
RemoteAuthorityResolverError: extHostTypes.RemoteAuthorityResolverError,
ResolvedAuthority: extHostTypes.ResolvedAuthority,
Selection: extHostTypes.Selection,
SelectionRange: extHostTypes.SelectionRange,
SemanticTokens: extHostTypes.SemanticTokens,
......@@ -1186,7 +1190,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
SignatureInformation: extHostTypes.SignatureInformation,
SnippetString: extHostTypes.SnippetString,
SourceBreakpoint: extHostTypes.SourceBreakpoint,
SourceControlInputBoxValidationType: extHostTypes.SourceControlInputBoxValidationType,
StandardTokenType: extHostTypes.StandardTokenType,
StatusBarAlignment: extHostTypes.StatusBarAlignment,
SymbolInformation: extHostTypes.SymbolInformation,
......@@ -1206,13 +1209,36 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
ThemeColor: extHostTypes.ThemeColor,
ThemeIcon: extHostTypes.ThemeIcon,
TreeItem: extHostTypes.TreeItem,
TreeItem2: extHostTypes.TreeItem,
TreeItemCollapsibleState: extHostTypes.TreeItemCollapsibleState,
UIKind: UIKind,
Uri: URI,
ViewColumn: extHostTypes.ViewColumn,
WorkspaceEdit: extHostTypes.WorkspaceEdit,
// proposed api types
get LogLevel() {
checkProposedApiEnabled(extension);
return extHostTypes.LogLevel;
},
get TreeItem2() {
checkProposedApiEnabled(extension);
return extHostTypes.TreeItem;
},
get RemoteAuthorityResolverError() {
checkProposedApiEnabled(extension);
return extHostTypes.RemoteAuthorityResolverError;
},
get ResolvedAuthority() {
checkProposedApiEnabled(extension);
return extHostTypes.ResolvedAuthority;
},
get SourceControlInputBoxValidationType() {
checkProposedApiEnabled(extension);
return extHostTypes.SourceControlInputBoxValidationType;
},
get ExtensionRuntime() {
checkProposedApiEnabled(extension);
return extHostTypes.ExtensionRuntime;
},
get TimelineItem() {
checkProposedApiEnabled(extension);
return extHostTypes.TimelineItem;
......
......@@ -1275,19 +1275,6 @@ export class CodeLens {
}
}
export class CodeInset {
range: Range;
height?: number;
constructor(range: Range, height?: number) {
this.range = range;
this.height = height;
}
}
@es5ClassCompat
export class MarkdownString extends BaseMarkdownString implements vscode.MarkdownString {
......
......@@ -63,7 +63,9 @@ class BulkEdit {
}
}
this._progress.report({ increment: 0, total: 100 });
// Show infinte progress when there is only 1 item since we do not know how long it takes
const increment = this._edits.length > 1 ? 0 : undefined;
this._progress.report({ increment, total: 100 });
// Increment by percentage points since progress API expects that
const progress: IProgress<void> = { report: _ => this._progress.report({ increment: 100 / this._edits.length }) };
......
......@@ -286,7 +286,7 @@ CommandsRegistry.registerCommand({
}
try {
await extensionManagementService.uninstall(extensionToUninstall, true);
await extensionManagementService.uninstall(extensionToUninstall);
} catch (e) {
onUnexpectedError(e);
throw e;
......
......@@ -347,7 +347,9 @@ export class InstallAction extends AbstractInstallAction {
if (server === this.extensionManagementServerService.remoteExtensionManagementServer) {
const host = this.extensionManagementServerService.remoteExtensionManagementServer.label;
this.label = isMachineScoped ? localize('install on remote and do not sync', "Install on {0} (Do not sync)", host) : localize('install on remote', "Install on {0}", host);
this.label = isMachineScoped
? localize({ key: 'install in remote and do not sync', comment: ['This is the name of the action to install an extension in remote server and do not sync it. Placeholder is for the name of remote server.'] }, "Install in {0} (Do not sync)", host)
: localize({ key: 'install in remote', comment: ['This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.'] }, "Install in {0}", host);
return;
}
......@@ -376,7 +378,7 @@ export class InstallAndSyncAction extends AbstractInstallAction {
) {
super(`extensions.installAndSync`, localize('install', "Install"), InstallAndSyncAction.Class,
extensionsWorkbenchService, instantiationService, runtimeExtensionService, workbenchThemeService, labelService);
this.tooltip = localize('install everywhere tooltip', "Install this extension in all your synced {0} instances", productService.nameLong);
this.tooltip = localize({ key: 'install everywhere tooltip', comment: ['Placeholder is the name of the product. Eg: Visual Studio Code or Visual Studio Code - Insiders'] }, "Install this extension in all your synced {0} instances", productService.nameLong);
this._register(Event.any(userDataAutoSyncEnablementService.onDidChangeEnablement,
Event.filter(userDataSyncResourceEnablementService.onDidChangeResourceEnablement, e => e[0] === SyncResource.Extensions))(() => this.update()));
}
......@@ -547,7 +549,9 @@ export class RemoteInstallAction extends InstallInOtherServerAction {
}
protected getInstallLabel(): string {
return this.extensionManagementServerService.remoteExtensionManagementServer ? localize('Install on Server', "Install in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label) : InstallInOtherServerAction.INSTALL_LABEL;
return this.extensionManagementServerService.remoteExtensionManagementServer
? localize({ key: 'install in remote', comment: ['This is the name of the action to install an extension in remote server. Placeholder is for the name of remote server.'] }, "Install in {0}", this.extensionManagementServerService.remoteExtensionManagementServer.label)
: InstallInOtherServerAction.INSTALL_LABEL;
}
}
......
......@@ -820,7 +820,7 @@ export class MaliciousExtensionChecker implements IWorkbenchContribution {
.filter(e => maliciousSet.has(e.identifier.id));
if (maliciousExtensions.length) {
return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e, true).then(() => {
return Promise.all(maliciousExtensions.map(e => this.extensionsManagementService.uninstall(e).then(() => {
this.notificationService.prompt(
Severity.Warning,
localize('malicious warning', "We have uninstalled '{0}' which was reported to be problematic.", e.identifier.id),
......
......@@ -878,7 +878,12 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
const notificationService = accessor.get(INotificationService);
const commandService = accessor.get(ICommandService);
const wasHidden = !viewsService.isViewVisible(VIEW_ID);
const view = await viewsService.openView(VIEW_ID, true);
if (wasHidden) {
// Give explorer some time to resolve itself #111218
await timeout(500);
}
if (!view) {
// Can happen in empty workspace case (https://github.com/microsoft/vscode/issues/100604)
......
......@@ -19,7 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { URI } from 'vs/base/common/uri';
import { ISCMService, ISCMRepository, ISCMProvider } from 'vs/workbench/contrib/scm/common/scm';
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
import { registerThemingParticipant, IColorTheme, ICssStyleCollector, themeColorFromId, IThemeService } from 'vs/platform/theme/common/themeService';
import { registerThemingParticipant, IColorTheme, ICssStyleCollector, themeColorFromId, IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { registerColor, transparent } from 'vs/platform/theme/common/colorRegistry';
import { Color, RGBA } from 'vs/base/common/color';
import { ICodeEditor, IEditorMouseEvent, MouseTargetType } from 'vs/editor/browser/editorBrowser';
......@@ -48,6 +48,7 @@ import { ISplice } from 'vs/base/common/sequence';
import { createStyleSheet } from 'vs/base/browser/dom';
import { ITextFileEditorModel, IResolvedTextFileEditorModel, ITextFileService, isTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { EncodingMode } from 'vs/workbench/common/editor';
import { gotoNextLocation, gotoPreviousLocation } from 'vs/platform/theme/common/iconRegistry';
class DiffActionRunner extends ActionRunner {
......@@ -246,8 +247,8 @@ class DirtyDiffWidget extends PeekViewWidget {
protected _fillHead(container: HTMLElement): void {
super._fillHead(container);
const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), 'codicon-arrow-up');
const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), 'codicon-arrow-down');
const previous = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowPreviousChangeAction(), ThemeIcon.asClassName(gotoPreviousLocation));
const next = this.instantiationService.createInstance(UIEditorAction, this.editor, new ShowNextChangeAction(), ThemeIcon.asClassName(gotoNextLocation));
this._disposables.add(previous);
this._disposables.add(next);
......
......@@ -15,8 +15,9 @@ import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
import { Disposable } from 'vs/base/common/lifecycle';
import { Event } from 'vs/base/common/event';
import { INotificationService } from 'vs/platform/notification/common/notification';
import { Codicon, registerIcon } from 'vs/base/common/codicons';
import { Codicon } from 'vs/base/common/codicons';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
export const QUICKOPEN_DETAIL_CONFIG = 'task.quickOpen.detail';
export const QUICKOPEN_SKIP_CONFIG = 'task.quickOpen.skip';
......
......@@ -36,8 +36,7 @@ import { IQuickAccessRegistry, Extensions as QuickAccessExtensions } from 'vs/pl
import { TerminalQuickAccessProvider } from 'vs/workbench/contrib/terminal/browser/terminalQuickAccess';
import { terminalConfiguration } from 'vs/workbench/contrib/terminal/common/terminalConfiguration';
import { CONTEXT_ACCESSIBILITY_MODE_ENABLED } from 'vs/platform/accessibility/common/accessibility';
import { Codicon } from 'vs/base/common/codicons';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
import { terminalViewIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
// Register services
registerSingleton(ITerminalService, TerminalService, true);
......@@ -61,8 +60,6 @@ CommandsRegistry.registerCommand({ id: quickAccessNavigatePreviousInTerminalPick
const configurationRegistry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
configurationRegistry.registerConfiguration(terminalConfiguration);
const terminalViewIcon = registerIcon('terminal-view-icon', Codicon.terminal, nls.localize('terminalViewIcon', 'View icon of the terminal view.'));
// Register views
const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
id: TERMINAL_VIEW_ID,
......
......@@ -9,7 +9,7 @@ import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService
import { TERMINAL_VIEW_ID, ITerminalConfigHelper, TitleEventSource, TERMINAL_COMMAND_ID, KEYBINDING_CONTEXT_TERMINAL_FIND_FOCUSED, TERMINAL_ACTION_CATEGORY, KEYBINDING_CONTEXT_TERMINAL_FOCUS, KEYBINDING_CONTEXT_TERMINAL_FIND_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_TEXT_SELECTED, KEYBINDING_CONTEXT_TERMINAL_FIND_NOT_VISIBLE, KEYBINDING_CONTEXT_TERMINAL_A11Y_TREE_FOCUS, KEYBINDING_CONTEXT_TERMINAL_PROCESS_SUPPORTED, IRemoteTerminalAttachTarget } from 'vs/workbench/contrib/terminal/common/terminal';
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
import { attachSelectBoxStyler, attachStylerCallback } from 'vs/platform/theme/common/styler';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { IThemeService, ThemeIcon } from 'vs/platform/theme/common/themeService';
import { IQuickInputService, IPickOptions, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { IContextViewService } from 'vs/platform/contextview/browser/contextView';
......@@ -42,6 +42,8 @@ import { FindInFilesCommand, IFindInFilesArgs } from 'vs/workbench/contrib/searc
import { ILabelService } from 'vs/platform/label/common/label';
import { RemoteNameContext } from 'vs/workbench/browser/contextkeys';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { killTerminalIcon, newTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
import { Codicon } from 'vs/base/common/codicons';
async function getCwdForSplit(configHelper: ITerminalConfigHelper, instance: ITerminalInstance, folders?: IWorkspaceFolder[], commandService?: ICommandService): Promise<string | URI | undefined> {
switch (configHelper.config.splitCwd) {
......@@ -96,7 +98,7 @@ export class KillTerminalAction extends Action {
id: string, label: string,
@ITerminalService private readonly _terminalService: ITerminalService
) {
super(id, label, 'terminal-action codicon-trash');
super(id, label, 'terminal-action ' + ThemeIcon.asClassName(killTerminalIcon));
}
async run() {
......@@ -176,7 +178,7 @@ export class CreateNewTerminalAction extends Action {
@ICommandService private readonly _commandService: ICommandService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService
) {
super(id, label, 'terminal-action codicon-add');
super(id, label, 'terminal-action ' + ThemeIcon.asClassName(newTerminalIcon));
}
async run(event?: any) {
......@@ -217,8 +219,8 @@ export class SplitTerminalAction extends Action {
public static readonly ID = TERMINAL_COMMAND_ID.SPLIT;
public static readonly LABEL = localize('workbench.action.terminal.split', "Split Terminal");
public static readonly SHORT_LABEL = localize('workbench.action.terminal.split.short', "Split");
public static readonly HORIZONTAL_CLASS = 'terminal-action codicon-split-horizontal';
public static readonly VERTICAL_CLASS = 'terminal-action codicon-split-vertical';
public static readonly HORIZONTAL_CLASS = 'terminal-action ' + Codicon.splitHorizontal.classNames;
public static readonly VERTICAL_CLASS = 'terminal-action ' + Codicon.splitVertical.classNames;
constructor(
id: string, label: string,
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Codicon } from 'vs/base/common/codicons';
import { localize } from 'vs/nls';
import { registerIcon } from 'vs/platform/theme/common/iconRegistry';
export const terminalViewIcon = registerIcon('terminal-view-icon', Codicon.terminal, localize('terminalViewIcon', 'View icon of the terminal view.'));
export const renameTerminalIcon = registerIcon('terminal-rename', Codicon.gear, localize('renameTerminalIcon', 'Icon for rename in the terminal quick menu.'));
export const killTerminalIcon = registerIcon('terminal-kill', Codicon.trash, localize('killTerminalIcon', 'Icon for killing a terminal instance.'));
export const newTerminalIcon = registerIcon('terminal-new', Codicon.add, localize('newTerminalIcon', 'Icon for creating a new terminal instance.'));
......@@ -10,6 +10,8 @@ import { matchesFuzzy } from 'vs/base/common/filters';
import { ITerminalService } from 'vs/workbench/contrib/terminal/browser/terminal';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { TERMINAL_COMMAND_ID } from 'vs/workbench/contrib/terminal/common/terminal';
import { ThemeIcon } from 'vs/platform/theme/common/themeService';
import { killTerminalIcon, renameTerminalIcon } from 'vs/workbench/contrib/terminal/browser/terminalIcons';
export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPickerQuickAccessItem> {
......@@ -39,11 +41,11 @@ export class TerminalQuickAccessProvider extends PickerQuickAccessProvider<IPick
highlights: { label: highlights },
buttons: [
{
iconClass: 'codicon-gear',
iconClass: ThemeIcon.asClassName(renameTerminalIcon),
tooltip: localize('renameTerminal', "Rename Terminal")
},
{
iconClass: 'codicon-trash',
iconClass: ThemeIcon.asClassName(killTerminalIcon),
tooltip: localize('killTerminal', "Kill Terminal Instance")
}
],
......
......@@ -5,7 +5,7 @@
import { Event, EventMultiplexer } from 'vs/base/common/event';
import {
ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions
ILocalExtension, IGalleryExtension, InstallExtensionEvent, DidInstallExtensionEvent, IExtensionIdentifier, DidUninstallExtensionEvent, IReportedExtension, IGalleryMetadata, IExtensionGalleryService, INSTALL_ERROR_NOT_SUPPORTED, InstallOptions, UninstallOptions
} from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionManagementServer, IExtensionManagementServerService, IWorkbenchExtensioManagementService } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { ExtensionType, isLanguagePackExtension, IExtensionManifest } from 'vs/platform/extensions/common/extensions';
......@@ -68,7 +68,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
return flatten(result);
}
async uninstall(extension: ILocalExtension): Promise<void> {
async uninstall(extension: ILocalExtension, options?: UninstallOptions): Promise<void> {
const server = this.getServer(extension);
if (!server) {
return Promise.reject(`Invalid location ${extension.location.toString()}`);
......@@ -77,7 +77,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
if (isLanguagePackExtension(extension.manifest)) {
return this.uninstallEverywhere(extension);
}
return this.uninstallInServer(extension, server);
return this.uninstallInServer(extension, server, options);
}
return server.extensionManagementService.uninstall(extension);
}
......@@ -101,7 +101,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
return promise;
}
private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, force?: boolean): Promise<void> {
private async uninstallInServer(extension: ILocalExtension, server: IExtensionManagementServer, options?: UninstallOptions): Promise<void> {
if (server === this.extensionManagementServerService.localExtensionManagementServer) {
const installedExtensions = await this.extensionManagementServerService.remoteExtensionManagementServer!.extensionManagementService.getInstalled(ExtensionType.User);
const dependentNonUIExtensions = installedExtensions.filter(i => !prefersExecuteOnUI(i.manifest, this.productService, this.configurationService)
......@@ -110,7 +110,7 @@ export class ExtensionManagementService extends Disposable implements IWorkbench
return Promise.reject(new Error(this.getDependentsErrorMessage(extension, dependentNonUIExtensions)));
}
}
return server.extensionManagementService.uninstall(extension, force);
return server.extensionManagementService.uninstall(extension, options);
}
private getDependentsErrorMessage(extension: ILocalExtension, dependents: ILocalExtension[]): string {
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册