Merge branch 'ads-main-vscode-2020-07-15T23-51-12' into main

This commit is contained in:
cssuh
2020-07-17 15:00:28 -04:00
558 changed files with 15180 additions and 8232 deletions

View File

@@ -6,7 +6,7 @@
import * as nls from 'vs/nls';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes';
import { AuthenticationSession, AuthenticationSessionsChangeEvent, AuthenticationProviderInformation } from 'vs/editor/common/modes';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { MainThreadAuthenticationProvider } from 'vs/workbench/api/browser/mainThreadAuthentication';
@@ -28,12 +28,12 @@ export interface IAuthenticationService {
requestNewSession(id: string, scopes: string[], extensionId: string, extensionName: string): void;
sessionsUpdate(providerId: string, event: AuthenticationSessionsChangeEvent): void;
readonly onDidRegisterAuthenticationProvider: Event<string>;
readonly onDidUnregisterAuthenticationProvider: Event<string>;
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation>;
readonly onDidChangeSessions: Event<{ providerId: string, event: AuthenticationSessionsChangeEvent }>;
readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }>;
getSessions(providerId: string): Promise<ReadonlyArray<AuthenticationSession>>;
getDisplayName(providerId: string): string;
getLabel(providerId: string): string;
supportsMultipleAccounts(providerId: string): boolean;
login(providerId: string, scopes: string[]): Promise<AuthenticationSession>;
logout(providerId: string, sessionId: string): Promise<void>;
@@ -77,14 +77,14 @@ export class AuthenticationService extends Disposable implements IAuthentication
private _authenticationProviders: Map<string, MainThreadAuthenticationProvider> = new Map<string, MainThreadAuthenticationProvider>();
private _onDidRegisterAuthenticationProvider: Emitter<string> = this._register(new Emitter<string>());
readonly onDidRegisterAuthenticationProvider: Event<string> = this._onDidRegisterAuthenticationProvider.event;
private _onDidRegisterAuthenticationProvider: Emitter<AuthenticationProviderInformation> = this._register(new Emitter<AuthenticationProviderInformation>());
readonly onDidRegisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this._onDidRegisterAuthenticationProvider.event;
private _onDidUnregisterAuthenticationProvider: Emitter<string> = this._register(new Emitter<string>());
readonly onDidUnregisterAuthenticationProvider: Event<string> = this._onDidUnregisterAuthenticationProvider.event;
private _onDidUnregisterAuthenticationProvider: Emitter<AuthenticationProviderInformation> = this._register(new Emitter<AuthenticationProviderInformation>());
readonly onDidUnregisterAuthenticationProvider: Event<AuthenticationProviderInformation> = this._onDidUnregisterAuthenticationProvider.event;
private _onDidChangeSessions: Emitter<{ providerId: string, event: AuthenticationSessionsChangeEvent }> = this._register(new Emitter<{ providerId: string, event: AuthenticationSessionsChangeEvent }>());
readonly onDidChangeSessions: Event<{ providerId: string, event: AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event;
private _onDidChangeSessions: Emitter<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }> = this._register(new Emitter<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }>());
readonly onDidChangeSessions: Event<{ providerId: string, label: string, event: AuthenticationSessionsChangeEvent }> = this._onDidChangeSessions.event;
constructor(@IActivityService private readonly activityService: IActivityService) {
super();
@@ -134,7 +134,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
registerAuthenticationProvider(id: string, authenticationProvider: MainThreadAuthenticationProvider): void {
this._authenticationProviders.set(id, authenticationProvider);
this._onDidRegisterAuthenticationProvider.fire(id);
this._onDidRegisterAuthenticationProvider.fire({ id, label: authenticationProvider.label });
if (this._placeholderMenuItem) {
this._placeholderMenuItem.dispose();
@@ -149,7 +149,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
if (provider) {
provider.dispose();
this._authenticationProviders.delete(id);
this._onDidUnregisterAuthenticationProvider.fire(id);
this._onDidUnregisterAuthenticationProvider.fire({ id, label: provider.label });
this.updateAccountsMenuItem();
}
@@ -165,9 +165,9 @@ export class AuthenticationService extends Disposable implements IAuthentication
}
async sessionsUpdate(id: string, event: AuthenticationSessionsChangeEvent): Promise<void> {
this._onDidChangeSessions.fire({ providerId: id, event: event });
const provider = this._authenticationProviders.get(id);
if (provider) {
this._onDidChangeSessions.fire({ providerId: id, label: provider.label, event: event });
await provider.updateSessionItems(event);
this.updateAccountsMenuItem();
@@ -187,7 +187,7 @@ export class AuthenticationService extends Disposable implements IAuthentication
let changed = false;
Object.keys(existingRequestsForProvider).forEach(requestedScopes => {
if (sessions.some(session => session.scopes.sort().join('') === requestedScopes)) {
if (sessions.some(session => session.scopes.slice().sort().join('') === requestedScopes)) {
// Request has been completed
changed = true;
const sessionRequest = existingRequestsForProvider[requestedScopes];
@@ -255,10 +255,10 @@ export class AuthenticationService extends Disposable implements IAuthentication
const session = await authenticationService.login(providerId, scopes);
// Add extension to allow list since user explicitly signed in on behalf of it
const allowList = readAllowedExtensions(storageService, providerId, session.account.displayName);
const allowList = readAllowedExtensions(storageService, providerId, session.account.label);
if (!allowList.find(allowed => allowed.id === extensionId)) {
allowList.push({ id: extensionId, name: extensionName });
storageService.store(`${providerId}-${session.account.displayName}`, JSON.stringify(allowList), StorageScope.GLOBAL);
storageService.store(`${providerId}-${session.account.label}`, JSON.stringify(allowList), StorageScope.GLOBAL);
}
// And also set it as the preferred account for the extension
@@ -295,10 +295,10 @@ export class AuthenticationService extends Disposable implements IAuthentication
this._badgeDisposable = this.activityService.showAccountsActivity({ badge });
}
}
getDisplayName(id: string): string {
getLabel(id: string): string {
const authProvider = this._authenticationProviders.get(id);
if (authProvider) {
return authProvider.displayName;
return authProvider.label;
} else {
throw new Error(`No authentication provider '${id}' is currently registered.`);
}

View File

@@ -9,7 +9,7 @@ import * as dom from 'vs/base/browser/dom';
import { IContextMenuService, IContextViewService } from 'vs/platform/contextview/browser/contextView';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { webFrame } from 'vs/base/parts/sandbox/electron-sandbox/globals';
import { getZoomFactor } from 'vs/base/browser/browser';
import { unmnemonicLabel } from 'vs/base/common/labels';
import { Event, Emitter } from 'vs/base/common/event';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -90,11 +90,13 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
const menu = this.createMenu(delegate, actions, onHide);
const anchor = delegate.getAnchor();
let x: number, y: number;
let zoom = webFrame.getZoomFactor();
let x: number;
let y: number;
const zoom = getZoomFactor();
if (dom.isHTMLElement(anchor)) {
let elementPosition = dom.getDomNodePagePosition(anchor);
const elementPosition = dom.getDomNodePagePosition(anchor);
x = elementPosition.left;
y = elementPosition.top + elementPosition.height;
@@ -118,8 +120,7 @@ class NativeContextMenuService extends Disposable implements IContextMenuService
x: Math.floor(x),
y: Math.floor(y),
positioningItem: delegate.autoSelectFirstItem ? 0 : undefined,
onHide: () => onHide()
});
}, () => onHide());
}
}

View File

@@ -230,12 +230,12 @@ export abstract class AbstractFileDialogService implements IFileDialogService {
return remoteFileDialog.showSaveDialog(options);
}
protected getSchemeFilterForWindow(): string {
return !this.environmentService.configuration.remoteAuthority ? Schemas.file : REMOTE_HOST_SCHEME;
protected getSchemeFilterForWindow(defaultUriScheme?: string): string {
return !this.environmentService.configuration.remoteAuthority ? (!defaultUriScheme || defaultUriScheme === Schemas.file ? Schemas.file : defaultUriScheme) : REMOTE_HOST_SCHEME;
}
protected getFileSystemSchema(options: { availableFileSystems?: readonly string[], defaultUri?: URI }): string {
return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow();
return options.availableFileSystems && options.availableFileSystems[0] || this.getSchemeFilterForWindow(options.defaultUri?.scheme);
}
abstract pickFileFolderAndOpen(options: IPickAndOpenOptions): Promise<void>;

View File

@@ -22,7 +22,7 @@ import { Schemas } from 'vs/base/common/network';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey';
import { equalsIgnoreCase, format, startsWithIgnoreCase, startsWith } from 'vs/base/common/strings';
import { equalsIgnoreCase, format, startsWithIgnoreCase } from 'vs/base/common/strings';
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { isValidBasename } from 'vs/base/common/extpath';
@@ -207,7 +207,7 @@ export class SimpleFileDialog {
}
private remoteUriFrom(path: string): URI {
if (!startsWith(path, '\\\\')) {
if (!path.startsWith('\\\\')) {
path = path.replace(/\\/g, '/');
}
const uri: URI = this.scheme === Schemas.file ? URI.file(path) : URI.from({ scheme: this.scheme, path });

View File

@@ -490,10 +490,10 @@ export class EditorService extends Disposable implements EditorServiceImpl {
return toDisposable(() => remove());
}
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] {
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][] {
const overrides = [];
for (const handler of this.openEditorHandlers) {
const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(resource, options, group, id).map(val => [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]) : [];
const handlers = handler.getEditorOverrides ? handler.getEditorOverrides(resource, options, group).map(val => [handler, val] as [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]) : [];
overrides.push(...handlers);
}
@@ -1347,7 +1347,7 @@ export class DelegatingEditorService implements IEditorService {
isOpen(editor: IEditorInput | IResourceEditorInput): boolean { return this.editorService.isOpen(editor as IResourceEditorInput /* TS fail */); }
overrideOpenEditor(handler: IOpenEditorOverrideHandler): IDisposable { return this.editorService.overrideOpenEditor(handler); }
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined) { return this.editorService.getEditorOverrides(resource, options, group, id); }
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined) { return this.editorService.getEditorOverrides(resource, options, group); }
invokeWithinEditorContext<T>(fn: (accessor: ServicesAccessor) => T): T { return this.editorService.invokeWithinEditorContext(fn); }

View File

@@ -42,14 +42,16 @@ export async function openEditorWith(
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
}
const allEditorOverrides = getAllAvailableEditors(resource, id, options, group, editorService);
const overrideOptions = { ...options, override: id };
const allEditorOverrides = getAllAvailableEditors(resource, id, overrideOptions, group, editorService);
if (!allEditorOverrides.length) {
return undefined; // {{SQL CARBON EDIT}} strict-null-checks
}
const overrideToUse = typeof id === 'string' && allEditorOverrides.find(([_, entry]) => entry.id === id);
if (overrideToUse) {
return overrideToUse[0].open(input, { ...options, override: id }, group, OpenEditorContext.NEW_EDITOR)?.override;
return overrideToUse[0].open(input, overrideOptions, group, OpenEditorContext.NEW_EDITOR)?.override;
}
// Prompt
@@ -134,7 +136,7 @@ export function getAllAvailableEditors(
editorService: IEditorService
): Array<[IOpenEditorOverrideHandler, IOpenEditorOverrideEntry]> {
const fileEditorInputFactory = Registry.as<IEditorInputFactoryRegistry>(EditorExtensions.EditorInputFactories).getFileEditorInputFactory();
const overrides = editorService.getEditorOverrides(resource, options, group, id);
const overrides = editorService.getEditorOverrides(resource, options, group);
if (!overrides.some(([_, entry]) => entry.id === DEFAULT_EDITOR_ID)) {
overrides.unshift([
{

View File

@@ -36,7 +36,7 @@ export interface IOpenEditorOverrideEntry {
export interface IOpenEditorOverrideHandler {
open(editor: IEditorInput, options: IEditorOptions | ITextEditorOptions | undefined, group: IEditorGroup, context: OpenEditorContext): IOpenEditorOverride | undefined;
getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined): IOpenEditorOverrideEntry[];
getEditorOverrides?(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): IOpenEditorOverrideEntry[];
}
export interface IOpenEditorOverride {
@@ -236,7 +236,7 @@ export interface IEditorService {
/**
* Get all available editor overrides for the editor input.
*/
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined, id: string | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][];
getEditorOverrides(resource: URI, options: IEditorOptions | undefined, group: IEditorGroup | undefined): [IOpenEditorOverrideHandler, IOpenEditorOverrideEntry][];
/**
* Allows to override the opening of editors by installing a handler that will

View File

@@ -133,6 +133,12 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
@memoize
get snippetsHome(): URI { return joinPath(this.userRoamingDataHome, 'snippets'); }
@memoize
get globalStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'globalStorage'); }
@memoize
get workspaceStorageHome(): URI { return URI.joinPath(this.userRoamingDataHome, 'workspaceStorage'); }
/*
* In Web every workspace can potentially have scoped user-data and/or extensions and if Sync state is shared then it can make
* Sync error prone - say removing extensions from another workspace. Hence scope Sync state per workspace.
@@ -208,10 +214,14 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
get disableExtensions() { return this.payload?.get('disableExtensions') === 'true'; }
private get webviewEndpoint(): string {
// TODO@matt: get fallback from product.json
return this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}';
}
@memoize
get webviewExternalEndpoint(): string {
// TODO@matt: get fallback from product.json
return (this.options.webviewEndpoint || 'https://{{uuid}}.vscode-webview-test.com/{{commit}}').replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37');
return (this.webviewEndpoint).replace('{{commit}}', product.commit || '0d728c31ebdf03869d2687d9be0b017667c9ff37');
}
@memoize
@@ -221,7 +231,8 @@ export class BrowserWorkbenchEnvironmentService implements IWorkbenchEnvironment
@memoize
get webviewCspSource(): string {
return this.webviewExternalEndpoint.replace('{{uuid}}', '*');
const uri = URI.parse(this.webviewEndpoint.replace('{{uuid}}', '*'));
return `${uri.scheme}://${uri.authority}`;
}
get disableTelemetry(): boolean { return false; }

View File

@@ -19,6 +19,7 @@ import { prefersExecuteOnUI, canExecuteOnWorkspace, prefersExecuteOnWorkspace, c
import { IProductService } from 'vs/platform/product/common/productService';
import { Schemas } from 'vs/base/common/network';
import { IDownloadService } from 'vs/platform/download/common/download';
import { flatten } from 'vs/base/common/arrays';
export class ExtensionManagementService extends Disposable implements IExtensionManagementService {
@@ -55,11 +56,9 @@ export class ExtensionManagementService extends Disposable implements IExtension
this.onDidUninstallExtension = this._register(this.servers.reduce((emitter: EventMultiplexer<DidUninstallExtensionEvent>, server) => { emitter.add(server.extensionManagementService.onDidUninstallExtension); return emitter; }, new EventMultiplexer<DidUninstallExtensionEvent>())).event;
}
getInstalled(type?: ExtensionType): Promise<ILocalExtension[]> {
const installedExtensions: ILocalExtension[] = [];
return Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type).then(extensions => installedExtensions.push(...extensions))))
.then(_ => installedExtensions)
.catch(e => installedExtensions);
async getInstalled(type?: ExtensionType): Promise<ILocalExtension[]> {
const result = await Promise.all(this.servers.map(({ extensionManagementService }) => extensionManagementService.getInstalled(type)));
return flatten(result);
}
async uninstall(extension: ILocalExtension): Promise<void> {

View File

@@ -48,7 +48,7 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
private readonly systemExtensionsPromise: Promise<IScannedExtension[]>;
private readonly staticExtensions: IScannedExtension[];
private readonly extensionsResource: URI;
private readonly extensionsResource: URI | undefined;
private readonly userExtensionsResourceLimiter: Queue<IUserExtension[]>;
constructor(
@@ -58,7 +58,7 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
@IRequestService private readonly requestService: IRequestService,
@ILogService private readonly logService: ILogService,
) {
this.extensionsResource = joinPath(environmentService.userRoamingDataHome, 'extensions.json');
this.extensionsResource = isWeb ? joinPath(environmentService.userRoamingDataHome, 'extensions.json') : undefined;
this.userExtensionsResourceLimiter = new Queue<IUserExtension[]>();
this.systemExtensionsPromise = isWeb ? this.builtinExtensionsScannerService.scanBuiltinExtensions() : Promise.resolve([]);
const staticExtensions = environmentService.options && Array.isArray(environmentService.options.staticExtensions) ? environmentService.options.staticExtensions : [];
@@ -155,10 +155,13 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
return null;
}
private readUserExtensions(): Promise<IUserExtension[]> {
private async readUserExtensions(): Promise<IUserExtension[]> {
if (!this.extensionsResource) {
return [];
}
return this.userExtensionsResourceLimiter.queue(async () => {
try {
const content = await this.fileService.readFile(this.extensionsResource);
const content = await this.fileService.readFile(this.extensionsResource!);
const storedUserExtensions: IStoredUserExtension[] = JSON.parse(content.value.toString());
return storedUserExtensions.map(e => ({
identifier: e.identifier,
@@ -174,6 +177,9 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
}
private writeUserExtensions(userExtensions: IUserExtension[]): Promise<IUserExtension[]> {
if (!this.extensionsResource) {
throw new Error('unsupported');
}
return this.userExtensionsResourceLimiter.queue(async () => {
const storedUserExtensions: IStoredUserExtension[] = userExtensions.map(e => ({
identifier: e.identifier,
@@ -183,7 +189,7 @@ export class WebExtensionsScannerService implements IWebExtensionsScannerService
changelogUri: e.changelogUri?.toJSON(),
packageNLSUri: e.packageNLSUri?.toJSON(),
}));
await this.fileService.writeFile(this.extensionsResource, VSBuffer.fromString(JSON.stringify(storedUserExtensions)));
await this.fileService.writeFile(this.extensionsResource!, VSBuffer.fromString(JSON.stringify(storedUserExtensions)));
return userExtensions;
});
}

View File

@@ -140,10 +140,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAgentConnection.remoteAuthority),
pid: remoteEnv.pid,
appRoot: remoteEnv.appRoot,
appSettingsHome: remoteEnv.appSettingsHome,
extensionHostLogsPath: remoteEnv.extensionHostLogsPath,
globalStorageHome: remoteEnv.globalStorageHome,
userHome: remoteEnv.userHome,
workspaceStorageHome: remoteEnv.workspaceStorageHome,
extensions: remoteEnv.extensions,
allExtensions: remoteEnv.extensions.concat(localExtensions)
};

View File

@@ -4,15 +4,14 @@
*--------------------------------------------------------------------------------------------*/
import { localize } from 'vs/nls';
import { Action } from 'vs/base/common/actions';
import { IDisposable, toDisposable, combinedDisposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService, ExtensionsLabel } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IExtensionGalleryService, IExtensionIdentifier, IExtensionManagementService } from 'vs/platform/extensionManagement/common/extensionManagement';
import { IWorkbenchExtensionEnablementService, EnablementState } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { createDecorator, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
import { IURLHandler, IURLService, IOpenURLOptions } from 'vs/platform/url/common/url';
@@ -23,8 +22,7 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { Registry } from 'vs/platform/registry/common/platform';
import { IWorkbenchContribution, Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry } from 'vs/workbench/common/contributions';
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { IWorkbenchActionRegistry, Extensions as WorkbenchActionExtensions } from 'vs/workbench/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
@@ -383,39 +381,37 @@ class ExtensionUrlBootstrapHandler implements IWorkbenchContribution, IURLHandle
const workbenchRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
workbenchRegistry.registerWorkbenchContribution(ExtensionUrlBootstrapHandler, LifecyclePhase.Ready);
export class ManageAuthorizedExtensionURIsAction extends Action {
class ManageAuthorizedExtensionURIsAction extends Action2 {
static readonly ID = 'workbench.extensions.action.manageAuthorizedExtensionURIs';
static readonly LABEL = localize('manage', "Manage Authorized Extension URIs...");
private storage: ConfirmedExtensionIdStorage;
constructor(
id = ManageAuthorizedExtensionURIsAction.ID,
label = ManageAuthorizedExtensionURIsAction.LABEL,
@IStorageService readonly storageService: IStorageService,
@IQuickInputService private readonly quickInputService: IQuickInputService
) {
super(id, label, undefined, true);
this.storage = new ConfirmedExtensionIdStorage(storageService);
constructor() {
super({
id: 'workbench.extensions.action.manageAuthorizedExtensionURIs',
title: { value: localize('manage', "Manage Authorized Extension URIs..."), original: 'Manage Authorized Extension URIs...' },
category: { value: localize('extensions', "Extensions"), original: 'Extensions' },
f1: true
});
}
async run(): Promise<void> {
const items = this.storage.extensions.map(label => ({ label, picked: true } as IQuickPickItem));
async run(accessor: ServicesAccessor): Promise<void> {
const storageService = accessor.get(IStorageService);
const quickInputService = accessor.get(IQuickInputService);
const storage = new ConfirmedExtensionIdStorage(storageService);
const items = storage.extensions.map(label => ({ label, picked: true } as IQuickPickItem));
if (items.length === 0) {
return;
}
const result = await this.quickInputService.pick(items, { canPickMany: true });
const result = await quickInputService.pick(items, { canPickMany: true });
if (!result) {
return;
}
this.storage.set(result.map(item => item.label));
storage.set(result.map(item => item.label));
}
}
const actionRegistry = Registry.as<IWorkbenchActionRegistry>(WorkbenchActionExtensions.WorkbenchActions);
actionRegistry.registerWorkbenchAction(SyncActionDescriptor.from(ManageAuthorizedExtensionURIsAction), `Extensions: Manage Authorized Extension URIs...`, ExtensionsLabel);
registerAction2(ManageAuthorizedExtensionURIsAction);

View File

@@ -147,14 +147,14 @@ export class WebWorkerExtensionHost implements IExtensionHost {
vscodeVersion: this._productService.vscodeVersion, // {{SQL CARBON EDIT}} add vscode version
parentPid: -1,
environment: {
isExtensionDevelopmentDebug: false,
isExtensionDevelopmentDebug: false, //todo@jrieken web
appName: this._productService.nameLong,
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: URI.parse('fake:globalStorageHome'), //todo@joh URI.file(this._environmentService.globalStorageHome),
userHome: URI.parse('fake:userHome'), //todo@joh URI.file(this._environmentService.userHome),
globalStorageHome: this._environmentService.globalStorageHome,
workspaceStorageHome: this._environmentService.workspaceStorageHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
},

View File

@@ -7,7 +7,7 @@ import { FileSystemProviderCapabilities, IStat, FileType, FileDeleteOptions, Fil
import { Event } from 'vs/base/common/event';
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
import { URI } from 'vs/base/common/uri';
import { NotImplementedError } from 'vs/base/common/errors';
import { NotSupportedError } from 'vs/base/common/errors';
export class FetchFileSystemProvider implements IFileSystemProviderWithFileReadWriteCapability {
@@ -44,18 +44,18 @@ export class FetchFileSystemProvider implements IFileSystemProviderWithFileReadW
// error implementations
writeFile(_resource: URI, _content: Uint8Array, _opts: FileWriteOptions): Promise<void> {
throw new NotImplementedError();
throw new NotSupportedError();
}
readdir(_resource: URI): Promise<[string, FileType][]> {
throw new NotImplementedError();
throw new NotSupportedError();
}
mkdir(_resource: URI): Promise<void> {
throw new NotImplementedError();
throw new NotSupportedError();
}
delete(_resource: URI, _opts: FileDeleteOptions): Promise<void> {
throw new NotImplementedError();
throw new NotSupportedError();
}
rename(_from: URI, _to: URI, _opts: FileOverwriteOptions): Promise<void> {
throw new NotImplementedError();
throw new NotSupportedError();
}
}

View File

@@ -132,14 +132,13 @@ export class ExtensionHostMain {
private static _transform(initData: IInitData, rpcProtocol: RPCProtocol): IInitData {
initData.extensions.forEach((ext) => (<any>ext).extensionLocation = URI.revive(rpcProtocol.transformIncomingURIs(ext.extensionLocation)));
initData.environment.appRoot = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appRoot));
initData.environment.appSettingsHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.appSettingsHome));
const extDevLocs = initData.environment.extensionDevelopmentLocationURI;
if (extDevLocs) {
initData.environment.extensionDevelopmentLocationURI = extDevLocs.map(url => URI.revive(rpcProtocol.transformIncomingURIs(url)));
}
initData.environment.extensionTestsLocationURI = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.extensionTestsLocationURI));
initData.environment.globalStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.globalStorageHome));
initData.environment.userHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.userHome));
initData.environment.workspaceStorageHome = URI.revive(rpcProtocol.transformIncomingURIs(initData.environment.workspaceStorageHome));
initData.logsLocation = URI.revive(rpcProtocol.transformIncomingURIs(initData.logsLocation));
initData.logFile = URI.revive(rpcProtocol.transformIncomingURIs(initData.logFile));
initData.workspace = rpcProtocol.transformIncomingURIs(initData.workspace);

View File

@@ -395,7 +395,7 @@ registerAction2(class MeasureExtHostLatencyAction extends Action2 {
value: nls.localize('measureExtHostLatency', "Measure Extension Host Latency"),
original: 'Measure Extension Host Latency'
},
category: nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"),
category: { value: nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"), original: 'Developer' },
f1: true
});
}

View File

@@ -12,7 +12,7 @@ import { Extensions, IJSONContributionRegistry } from 'vs/platform/jsonschemas/c
import { Registry } from 'vs/platform/registry/common/platform';
import { IMessage } from 'vs/workbench/services/extensions/common/extensions';
import { ExtensionIdentifier, IExtensionDescription, EXTENSION_CATEGORIES } from 'vs/platform/extensions/common/extensions';
import { values } from 'vs/base/common/map';
import { toArray } from 'vs/base/common/arrays';
const schemaRegistry = Registry.as<IJSONContributionRegistry>(Extensions.JSONContribution);
export type ExtensionKind = 'workspace' | 'ui' | undefined;
@@ -444,7 +444,7 @@ export class ExtensionsRegistryImpl {
}
public getExtensionPoints(): ExtensionPoint<any>[] {
return values(this._extensionPoints);
return toArray(this._extensionPoints.values());
}
}

View File

@@ -36,10 +36,9 @@ export interface IRemoteExtensionHostInitData {
readonly connectionData: IRemoteConnectionData | null;
readonly pid: number;
readonly appRoot: URI;
readonly appSettingsHome: URI;
readonly extensionHostLogsPath: URI;
readonly globalStorageHome: URI;
readonly userHome: URI;
readonly workspaceStorageHome: URI;
readonly extensions: IExtensionDescription[];
readonly allExtensions: IExtensionDescription[];
}
@@ -216,14 +215,13 @@ export class RemoteExtensionHost extends Disposable implements IExtensionHost {
environment: {
isExtensionDevelopmentDebug,
appRoot: remoteInitData.appRoot,
appSettingsHome: remoteInitData.appSettingsHome,
appName: this._productService.nameLong,
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: remoteInitData.globalStorageHome,
userHome: remoteInitData.userHome,
workspaceStorageHome: remoteInitData.workspaceStorageHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
},

View File

@@ -17,7 +17,7 @@ import { IRemoteExtensionHostDataProvider, RemoteExtensionHost, IRemoteExtension
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
import { IRemoteAuthorityResolverService, RemoteAuthorityResolverError, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { getExtensionKind } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { IInstantiationService, ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { ILifecycleService, LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
@@ -34,13 +34,13 @@ import { flatten } from 'vs/base/common/arrays';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
import { Registry } from 'vs/platform/registry/common/platform';
import { Extensions as ActionExtensions, IWorkbenchActionRegistry } from 'vs/workbench/common/actions';
import { Action2, registerAction2 } from 'vs/platform/actions/common/actions';
import { getRemoteName } from 'vs/platform/remote/common/remoteHosts';
import { IRemoteAgentEnvironment } from 'vs/platform/remote/common/remoteAgentEnvironment';
import { WebWorkerExtensionHost } from 'vs/workbench/services/extensions/browser/webWorkerExtensionHost';
import { IExtensionActivationHost as IWorkspaceContainsActivationHost, checkGlobFileExists, checkActivateWorkspaceContainsExtension } from 'vs/workbench/api/common/shared/workspaceContains';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { exists } from 'vs/base/node/pfs';
class DeltaExtensionsQueueItem {
constructor(
@@ -75,6 +75,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
@IHostService private readonly _hostService: IHostService,
@IRemoteExplorerService private readonly _remoteExplorerService: IRemoteExplorerService,
@IExtensionGalleryService private readonly _extensionGalleryService: IExtensionGalleryService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
) {
super(
instantiationService,
@@ -313,6 +314,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
let shouldActivate = false;
let shouldActivateReason: string | null = null;
let hasWorkspaceContains = false;
if (Array.isArray(extensionDescription.activationEvents)) {
for (let activationEvent of extensionDescription.activationEvents) {
// TODO@joao: there's no easy way to contribute this
@@ -334,10 +336,7 @@ export class ExtensionService extends AbstractExtensionService implements IExten
}
if (/^workspaceContains/.test(activationEvent)) {
// do not trigger a search, just activate in this case...
shouldActivate = true;
shouldActivateReason = activationEvent;
break;
hasWorkspaceContains = true;
}
if (activationEvent === 'onStartupFinished') {
@@ -352,6 +351,24 @@ export class ExtensionService extends AbstractExtensionService implements IExten
await Promise.all(
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: shouldActivateReason! }))
).then(() => { });
} else if (hasWorkspaceContains) {
const workspace = await this._contextService.getCompleteWorkspace();
const forceUsingSearch = !!this._environmentService.configuration.remoteAuthority;
const host: IWorkspaceContainsActivationHost = {
folders: workspace.folders.map(folder => folder.uri),
forceUsingSearch: forceUsingSearch,
exists: (path) => exists(path),
checkExists: (folders, includes, token) => this._instantiationService.invokeFunction((accessor) => checkGlobFileExists(accessor, folders, includes, token))
};
const result = await checkActivateWorkspaceContainsExtension(host, extensionDescription);
if (!result) {
return;
}
await Promise.all(
this._extensionHostManagers.map(extHostManager => extHostManager.activate(extensionDescription.identifier, { startup: false, extensionId: extensionDescription.identifier, activationEvent: result.activationEvent }))
).then(() => { });
}
}
@@ -560,10 +577,9 @@ export class ExtensionService extends AbstractExtensionService implements IExten
connectionData: this._remoteAuthorityResolverService.getConnectionData(remoteAuthority),
pid: remoteEnv.pid,
appRoot: remoteEnv.appRoot,
appSettingsHome: remoteEnv.appSettingsHome,
extensionHostLogsPath: remoteEnv.extensionHostLogsPath,
globalStorageHome: remoteEnv.globalStorageHome,
userHome: remoteEnv.userHome,
workspaceStorageHome: remoteEnv.workspaceStorageHome,
extensions: remoteExtensions,
allExtensions: this._registry.getAllExtensionDescriptions(),
});
@@ -715,23 +731,20 @@ function filterByRunningLocation(extensions: IExtensionDescription[], runningLoc
registerSingleton(IExtensionService, ExtensionService);
class RestartExtensionHostAction extends Action {
class RestartExtensionHostAction extends Action2 {
public static readonly ID = 'workbench.action.restartExtensionHost';
public static readonly LABEL = nls.localize('restartExtensionHost', "Restart Extension Host");
constructor(
id: string,
label: string,
@IExtensionService private readonly _extensionService: IExtensionService
) {
super(id, label);
constructor() {
super({
id: 'workbench.action.restartExtensionHost',
title: { value: nls.localize('restartExtensionHost', "Restart Extension Host"), original: 'Restart Extension Host' },
category: { value: nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"), original: 'Developer' },
f1: true
});
}
public async run() {
this._extensionService.restartExtensionHost();
run(accessor: ServicesAccessor): void {
accessor.get(IExtensionService).restartExtensionHost();
}
}
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
registry.registerWorkbenchAction(SyncActionDescriptor.from(RestartExtensionHostAction), 'Developer: Restart Extension Host', nls.localize({ key: 'developer', comment: ['A developer on Code itself or someone diagnosing issues in Code'] }, "Developer"));
registerAction2(RestartExtensionHostAction);

View File

@@ -431,14 +431,13 @@ export class LocalProcessExtensionHost implements IExtensionHost {
environment: {
isExtensionDevelopmentDebug: this._isExtensionDevDebug,
appRoot: this._environmentService.appRoot ? URI.file(this._environmentService.appRoot) : undefined,
appSettingsHome: this._environmentService.appSettingsHome ? this._environmentService.appSettingsHome : undefined,
appName: this._productService.nameLong,
appUriScheme: this._productService.urlProtocol,
appLanguage: platform.language,
extensionDevelopmentLocationURI: this._environmentService.extensionDevelopmentLocationURI,
extensionTestsLocationURI: this._environmentService.extensionTestsLocationURI,
globalStorageHome: URI.file(this._environmentService.globalStorageHome),
userHome: this._environmentService.userHome,
globalStorageHome: this._environmentService.globalStorageHome,
workspaceStorageHome: this._environmentService.workspaceStorageHome,
webviewResourceRoot: this._environmentService.webviewResourceRoot,
webviewCspSource: this._environmentService.webviewCspSource,
},

View File

@@ -12,8 +12,7 @@ import { areSameExtensions } from 'vs/platform/extensionManagement/common/extens
import { ILogService } from 'vs/platform/log/common/log';
import { toErrorMessage } from 'vs/base/common/errorMessage';
import { prefersExecuteOnUI } from 'vs/workbench/services/extensions/common/extensionsUtil';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/map';
import { isNonEmptyArray, toArray } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { localize } from 'vs/nls';
import { IProductService } from 'vs/platform/product/common/productService';
@@ -102,14 +101,14 @@ export class RemoteExtensionManagementChannelClient extends ExtensionManagementC
const result = new Map<string, IGalleryExtension>();
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, true, token);
return values(result);
return toArray(result.values());
}
private async getAllWorkspaceDependenciesAndPackedExtensions(manifest: IExtensionManifest, token: CancellationToken): Promise<IGalleryExtension[]> {
const result = new Map<string, IGalleryExtension>();
const extensions = [...(manifest.extensionPack || []), ...(manifest.extensionDependencies || [])];
await this.getDependenciesAndPackedExtensionsRecursively(extensions, result, false, token);
return values(result);
return toArray(result.values());
}
private async getDependenciesAndPackedExtensionsRecursively(toGet: string[], result: Map<string, IGalleryExtension>, uiExtension: boolean, token: CancellationToken): Promise<void> {

View File

@@ -20,9 +20,11 @@ import { IURITransformer, URITransformer, IRawURITransformer } from 'vs/base/com
import { exists } from 'vs/base/node/pfs';
import { realpath } from 'vs/base/node/extpath';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import 'vs/workbench/api/node/extHost.services';
import { RunOnceScheduler } from 'vs/base/common/async';
import 'vs/workbench/api/common/extHost.common.services';
import 'vs/workbench/api/node/extHost.node.services';
interface ParsedExtHostArgs {
uriTransformerPath?: string;
useHostProxy?: string;

View File

@@ -1,46 +0,0 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtHostOutputService, ExtHostOutputService } from 'vs/workbench/api/common/extHostOutput';
import { IExtHostWorkspace, ExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { IExtHostDecorations, ExtHostDecorations } from 'vs/workbench/api/common/extHostDecorations';
import { IExtHostConfiguration, ExtHostConfiguration } from 'vs/workbench/api/common/extHostConfiguration';
import { IExtHostCommands, ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import { IExtHostTerminalService, WorkerExtHostTerminalService } from 'vs/workbench/api/common/extHostTerminalService';
// import { IExtHostTask, WorkerExtHostTask } from 'vs/workbench/api/common/extHostTask';
// import { IExtHostDebugService, WorkerExtHostDebugService } from 'vs/workbench/api/common/extHostDebugService';
import { IExtHostSearch, ExtHostSearch } from 'vs/workbench/api/common/extHostSearch';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
import { IExtHostExtensionService } from 'vs/workbench/api/common/extHostExtensionService';
import { IExtHostStorage, ExtHostStorage } from 'vs/workbench/api/common/extHostStorage';
import { ExtHostExtensionService } from 'vs/workbench/api/worker/extHostExtensionService';
import { ILogService } from 'vs/platform/log/common/log';
import { ExtHostLogService } from 'vs/workbench/api/worker/extHostLogService';
import { IExtHostTunnelService, ExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
import { IExtHostApiDeprecationService, ExtHostApiDeprecationService, } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { IExtHostWindow, ExtHostWindow } from 'vs/workbench/api/common/extHostWindow';
import { NotImplementedProxy } from 'vs/base/common/types';
// register singleton services
registerSingleton(ILogService, ExtHostLogService);
registerSingleton(IExtHostApiDeprecationService, ExtHostApiDeprecationService);
registerSingleton(IExtHostOutputService, ExtHostOutputService);
registerSingleton(IExtHostWorkspace, ExtHostWorkspace);
registerSingleton(IExtHostWindow, ExtHostWindow);
registerSingleton(IExtHostDecorations, ExtHostDecorations);
registerSingleton(IExtHostConfiguration, ExtHostConfiguration);
registerSingleton(IExtHostCommands, ExtHostCommands);
registerSingleton(IExtHostDocumentsAndEditors, ExtHostDocumentsAndEditors);
registerSingleton(IExtHostStorage, ExtHostStorage);
registerSingleton(IExtHostExtensionService, ExtHostExtensionService);
registerSingleton(IExtHostSearch, ExtHostSearch);
registerSingleton(IExtHostTunnelService, ExtHostTunnelService);
registerSingleton(IExtHostTerminalService, WorkerExtHostTerminalService);
// registerSingleton(IExtHostTask, WorkerExtHostTask); {{SQL CARBON EDIT}} disable
// registerSingleton(IExtHostDebugService, WorkerExtHostDebugService); {{SQL CARBON EDIT}} disable
registerSingleton(IExtensionStoragePaths, class extends NotImplementedProxy<IExtensionStoragePaths>(String(IExtensionStoragePaths)) { whenReady = Promise.resolve(); });

View File

@@ -10,7 +10,9 @@ import { isMessageOfType, MessageType, createMessageOfType } from 'vs/workbench/
import { IInitData } from 'vs/workbench/api/common/extHost.protocol';
import { ExtensionHostMain } from 'vs/workbench/services/extensions/common/extensionHostMain';
import { IHostUtils } from 'vs/workbench/api/common/extHostExtensionService';
import 'vs/workbench/services/extensions/worker/extHost.services';
import 'vs/workbench/api/common/extHost.common.services';
import 'vs/workbench/api/worker/extHost.worker.services';
//#region --- Define, capture, and override some globals

View File

@@ -755,10 +755,12 @@ export class HistoryService extends Disposable implements IHistoryService {
private readonly canReopenClosedEditorContextKey = (new RawContextKey<boolean>('canReopenClosedEditor', false)).bindTo(this.contextKeyService);
private updateContextKeys(): void {
this.canNavigateBackContextKey.set(this.navigationStack.length > 0 && this.navigationStackIndex > 0);
this.canNavigateForwardContextKey.set(this.navigationStack.length > 0 && this.navigationStackIndex < this.navigationStack.length - 1);
this.canNavigateToLastEditLocationContextKey.set(!!this.lastEditLocation);
this.canReopenClosedEditorContextKey.set(this.recentlyClosedEditors.length > 0);
this.contextKeyService.bufferChangeEvents(() => {
this.canNavigateBackContextKey.set(this.navigationStack.length > 0 && this.navigationStackIndex > 0);
this.canNavigateForwardContextKey.set(this.navigationStack.length > 0 && this.navigationStackIndex < this.navigationStack.length - 1);
this.canNavigateToLastEditLocationContextKey.set(!!this.lastEditLocation);
this.canReopenClosedEditorContextKey.set(this.recentlyClosedEditors.length > 0);
});
}
//#endregion

View File

@@ -16,7 +16,7 @@ export interface IHoverService {
readonly _serviceBrand: undefined;
/**
* Shows a hover.
* Shows a hover, provided a hover with the same options object is not already visible.
* @param options A set of options defining the characteristics of the hover.
* @param focus Whether to focus the hover (useful for keyboard accessibility).
*

View File

@@ -6,6 +6,7 @@
import { localize } from 'vs/nls';
import { Queue } from 'vs/base/common/async';
import * as json from 'vs/base/common/json';
import * as objects from 'vs/base/common/objects';
import { setProperty } from 'vs/base/common/jsonEdit';
import { Edit } from 'vs/base/common/jsonFormatter';
import { Disposable, IReference } from 'vs/base/common/lifecycle';
@@ -143,7 +144,11 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
const eol = model.getEOL();
const key = keybindingItem.resolvedKeybinding ? keybindingItem.resolvedKeybinding.getUserSettingsLabel() : null;
if (key) {
this.applyEditsToBuffer(setProperty(model.getValue(), [-1], this.asObject(key, keybindingItem.command, keybindingItem.when ? keybindingItem.when.serialize() : undefined, true), { tabSize, insertSpaces, eol })[0], model);
const entry: IUserFriendlyKeybinding = this.asObject(key, keybindingItem.command, keybindingItem.when ? keybindingItem.when.serialize() : undefined, true);
const userKeybindingEntries = <IUserFriendlyKeybinding[]>json.parse(model.getValue());
if (userKeybindingEntries.every(e => !this.areSame(e, entry))) {
this.applyEditsToBuffer(setProperty(model.getValue(), [-1], entry, { tabSize, insertSpaces, eol })[0], model);
}
}
}
@@ -196,6 +201,26 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
return object;
}
private areSame(a: IUserFriendlyKeybinding, b: IUserFriendlyKeybinding): boolean {
if (a.command !== b.command) {
return false;
}
if (a.key !== b.key) {
return false;
}
const whenA = ContextKeyExpr.deserialize(a.when);
const whenB = ContextKeyExpr.deserialize(b.when);
if ((whenA && !whenB) || (!whenA && whenB)) {
return false;
}
if (whenA && whenB && !whenA.equals(whenB)) {
return false;
}
if (!objects.equals(a.args, b.args)) {
return false;
}
return true;
}
private applyEditsToBuffer(edit: Edit, model: ITextModel): void {
const startPosition = model.getPositionAt(edit.offset);
@@ -206,7 +231,6 @@ export class KeybindingsEditingService extends Disposable implements IKeybinding
model.pushEditOperations([new Selection(startPosition.lineNumber, startPosition.column, startPosition.lineNumber, startPosition.column)], [editOperation], () => []);
}
private resolveModelReference(): Promise<IReference<IResolvedTextEditorModel>> {
return this.fileService.exists(this.resource)
.then(exists => {

View File

@@ -220,6 +220,16 @@ suite('KeybindingsEditing', () => {
.then(() => assert.deepEqual(getUserKeybindings(), expected));
});
test('remove a default keybinding should not ad duplicate entries', async () => {
const expected: IUserFriendlyKeybinding[] = [{ key: 'alt+c', command: '-a' }];
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
await testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'a', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } } }));
assert.deepEqual(getUserKeybindings(), expected);
});
test('remove a user keybinding', () => {
writeToKeybindingsFile({ key: 'alt+c', command: 'b' });
return testObject.removeKeybinding(aResolvedKeybindingItem({ command: 'b', firstPart: { keyCode: KeyCode.KEY_C, modifiers: { altKey: true } }, isDefault: false }))

View File

@@ -12,7 +12,7 @@ import { Extensions as WorkbenchExtensions, IWorkbenchContributionsRegistry, IWo
import { Registry } from 'vs/platform/registry/common/platform';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService, IWorkspace } from 'vs/platform/workspace/common/workspace';
import { isEqual, basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources';
import { basenameOrAuthority, basename, joinPath, dirname } from 'vs/base/common/resources';
import { tildify, getPathLabel } from 'vs/base/common/labels';
import { IWorkspaceIdentifier, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier, WORKSPACE_EXTENSION, toWorkspaceIdentifier, isWorkspaceIdentifier, isUntitledWorkspace } from 'vs/platform/workspaces/common/workspaces';
import { ILabelService, ResourceLabelFormatter, ResourceLabelFormatting, IFormatterChangeEvent } from 'vs/platform/label/common/label';
@@ -141,21 +141,18 @@ export class LabelService extends Disposable implements ILabelService {
const baseResource = this.contextService?.getWorkspaceFolder(resource);
if (options.relative && baseResource) {
const rootName = baseResource?.name ?? basenameOrAuthority(baseResource.uri);
const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix);
let relativeLabel = this.formatUri(resource, formatting, options.noPrefix);
let relativeLabel: string;
if (isEqual(baseResource.uri, resource)) {
relativeLabel = ''; // no label if resources are identical
} else {
const baseResourceLabel = this.formatUri(baseResource.uri, formatting, options.noPrefix);
relativeLabel = this.formatUri(resource, formatting, options.noPrefix).substring(baseResourceLabel.lastIndexOf(formatting.separator) + 1);
if (relativeLabel.startsWith(rootName)) {
relativeLabel = relativeLabel.substring(rootName.length + (relativeLabel[rootName.length] === formatting.separator ? 1 : 0));
}
let overlap = 0;
while (relativeLabel[overlap] && relativeLabel[overlap] === baseResourceLabel[overlap]) { overlap++; }
if (!relativeLabel[overlap] || relativeLabel[overlap] === formatting.separator) {
relativeLabel = relativeLabel.substring(1 + overlap);
}
const hasMultipleRoots = this.contextService.getWorkspace().folders.length > 1;
if (hasMultipleRoots && !options.noPrefix) {
const rootName = baseResource?.name ?? basenameOrAuthority(baseResource.uri);
relativeLabel = relativeLabel ? (rootName + ' • ' + relativeLabel) : rootName; // always show root basename if there are multiple
}

View File

@@ -3,14 +3,15 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as resources from 'vs/base/common/resources';
import * as assert from 'assert';
import { TestEnvironmentService, TestPathService } from 'vs/workbench/test/browser/workbenchTestServices';
import { URI } from 'vs/base/common/uri';
import { LabelService } from 'vs/workbench/services/label/common/labelService';
import { TestContextService } from 'vs/workbench/test/common/workbenchTestServices';
import { Workspace, WorkspaceFolder } from 'vs/platform/workspace/common/workspace';
suite('URI Label', () => {
let labelService: LabelService;
setup(() => {
@@ -156,3 +157,75 @@ suite('URI Label', () => {
assert.equal(labelService.getUriLabel(uri1, { relative: false }), 'LABEL: /END');
});
});
suite('multi-root worksapce', () => {
let labelService: LabelService;
setup(() => {
const sources = URI.file('folder1/src');
const tests = URI.file('folder1/test');
const other = URI.file('folder2');
labelService = new LabelService(
TestEnvironmentService,
new TestContextService(
new Workspace('test-workspaace', [
new WorkspaceFolder({ uri: sources, index: 0, name: 'Sources' }, { uri: sources.toString() }),
new WorkspaceFolder({ uri: tests, index: 1, name: 'Tests' }, { uri: tests.toString() }),
new WorkspaceFolder({ uri: other, index: 2, name: resources.basename(other) }, { uri: other.toString() }),
])),
new TestPathService());
});
test('labels of files in multiroot workspaces are the foldername folloed by offset from the folder', () => {
labelService.registerFormatter({
scheme: 'file',
formatting: {
label: '${authority}${path}',
separator: '/',
tildify: false,
normalizeDriveLetter: false,
authorityPrefix: '//',
workspaceSuffix: ''
}
});
const tests = {
'folder1/src/file': 'Sources • file',
'folder1/src/folder/file': 'Sources • folder/file',
'folder1/src': 'Sources',
'folder1/other': '/folder1/other',
'folder2/other': 'folder2 • other',
};
Object.entries(tests).forEach(([path, label]) => {
const generated = labelService.getUriLabel(URI.file(path), { relative: true });
assert.equal(generated, label);
});
});
test('labels with context after path', () => {
labelService.registerFormatter({
scheme: 'file',
formatting: {
label: '${path} (${scheme})',
separator: '/',
}
});
const tests = {
'folder1/src/file': 'Sources • file (file)',
'folder1/src/folder/file': 'Sources • folder/file (file)',
'folder1/src': 'Sources',
'folder1/other': '/folder1/other (file)',
'folder2/other': 'folder2 • other (file)',
};
Object.entries(tests).forEach(([path, label]) => {
const generated = labelService.getUriLabel(URI.file(path), { relative: true });
assert.equal(generated, label, path);
});
});
});

View File

@@ -8,7 +8,6 @@ import { IStringDictionary } from 'vs/base/common/collections';
import { Emitter, Event } from 'vs/base/common/event';
import { JSONVisitor, visit } from 'vs/base/common/json';
import { Disposable, IReference } from 'vs/base/common/lifecycle';
import * as map from 'vs/base/common/map';
import { assign } from 'vs/base/common/objects';
import { URI } from 'vs/base/common/uri';
import { IRange, Range } from 'vs/editor/common/core/range';
@@ -49,7 +48,7 @@ export abstract class AbstractSettingsModel extends EditorModel {
*/
private removeDuplicateResults(): void {
const settingKeys = new Set<string>();
map.keys(this._currentResultGroups)
[...this._currentResultGroups.keys()]
.sort((a, b) => this._currentResultGroups.get(a)!.order - this._currentResultGroups.get(b)!.order)
.forEach(groupId => {
const group = this._currentResultGroups.get(groupId)!;
@@ -171,7 +170,7 @@ export class SettingsEditorModel extends AbstractSettingsModel implements ISetti
}
protected update(): IFilterResult | undefined {
const resultGroups = map.values(this._currentResultGroups);
const resultGroups = [...this._currentResultGroups.values()];
if (!resultGroups.length) {
return undefined;
}
@@ -752,8 +751,7 @@ export class DefaultSettingsEditorModel extends AbstractSettingsModel implements
}
// Grab current result groups, only render non-empty groups
const resultGroups = map
.values(this._currentResultGroups)
const resultGroups = [...this._currentResultGroups.values()]
.sort((a, b) => a.order - b.order);
const nonEmptyResultGroups = resultGroups.filter(group => group.result.filterMatches.length);

View File

@@ -525,7 +525,7 @@ export class ProgressService extends Disposable implements IProgressService {
keyEventProcessor: (event: StandardKeyboardEvent) => {
const resolved = this.keybindingService.softDispatch(event, this.layoutService.container);
if (resolved?.commandId) {
if (allowableCommands.indexOf(resolved.commandId) === -1) {
if (!allowableCommands.includes(resolved.commandId)) {
EventHelper.stop(event, true);
}
}

View File

@@ -21,12 +21,12 @@ export interface IRemoteAgentEnvironmentDTO {
pid: number;
connectionToken: string;
appRoot: UriComponents;
appSettingsHome: UriComponents;
settingsPath: UriComponents;
logsPath: UriComponents;
extensionsPath: UriComponents;
extensionHostLogsPath: UriComponents;
globalStorageHome: UriComponents;
workspaceStorageHome: UriComponents;
userHome: UriComponents;
extensions: IExtensionDescription[];
os: platform.OperatingSystem;
@@ -47,12 +47,12 @@ export class RemoteExtensionEnvironmentChannelClient {
pid: data.pid,
connectionToken: data.connectionToken,
appRoot: URI.revive(data.appRoot),
appSettingsHome: URI.revive(data.appSettingsHome),
settingsPath: URI.revive(data.settingsPath),
logsPath: URI.revive(data.logsPath),
extensionsPath: URI.revive(data.extensionsPath),
extensionHostLogsPath: URI.revive(data.extensionHostLogsPath),
globalStorageHome: URI.revive(data.globalStorageHome),
workspaceStorageHome: URI.revive(data.workspaceStorageHome),
userHome: URI.revive(data.userHome),
extensions: data.extensions.map(ext => { (<any>ext).extensionLocation = URI.revive(ext.extensionLocation); return ext; }),
os: data.os

View File

@@ -7,7 +7,7 @@ import * as arrays from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
import { Disposable, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { keys, ResourceMap } from 'vs/base/common/map';
import { ResourceMap } from 'vs/base/common/map';
import { Schemas } from 'vs/base/common/network';
import { StopWatch } from 'vs/base/common/stopwatch';
import { URI as uri } from 'vs/base/common/uri';
@@ -199,7 +199,7 @@ export class SearchService extends Disposable implements ISearchService {
const searchPs: Promise<ISearchComplete>[] = [];
const fqs = this.groupFolderQueriesByScheme(query);
await Promise.all(keys(fqs).map(async scheme => {
await Promise.all([...fqs.keys()].map(async scheme => {
const schemeFQs = fqs.get(scheme)!;
let provider = query.type === QueryType.File ?
this.fileSearchProviders.get(scheme) :

View File

@@ -722,16 +722,16 @@ export function rgErrorMsgForDisplay(msg: string): string | undefined {
const lines = msg.trim().split('\n');
const firstLine = lines[0].trim();
if (strings.startsWith(firstLine, 'Error parsing regex')) {
if (firstLine.startsWith('Error parsing regex')) {
return firstLine;
}
if (strings.startsWith(firstLine, 'regex parse error')) {
if (firstLine.startsWith('regex parse error')) {
return strings.uppercaseFirstLetter(lines[lines.length - 1].trim());
}
if (strings.startsWith(firstLine, 'error parsing glob') ||
strings.startsWith(firstLine, 'unsupported encoding')) {
if (firstLine.startsWith('error parsing glob') ||
firstLine.startsWith('unsupported encoding')) {
// Uppercase first letter
return firstLine.charAt(0).toUpperCase() + firstLine.substr(1);
}
@@ -741,7 +741,7 @@ export function rgErrorMsgForDisplay(msg: string): string | undefined {
return `Literal '\\n' currently not supported`;
}
if (strings.startsWith(firstLine, 'Literal ')) {
if (firstLine.startsWith('Literal ')) {
// Other unsupported chars
return firstLine;
}

View File

@@ -5,19 +5,18 @@
import * as fs from 'fs';
import * as gracefulFs from 'graceful-fs';
import { basename, dirname, join, sep } from 'vs/base/common/path';
import * as arrays from 'vs/base/common/arrays';
import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async';
import { CancellationToken } from 'vs/base/common/cancellation';
import { canceled } from 'vs/base/common/errors';
import { Emitter, Event } from 'vs/base/common/event';
import { compareItemsByFuzzyScore, FuzzyScorerCache, IItemAccessor, prepareQuery } from 'vs/base/common/fuzzyScorer';
import * as objects from 'vs/base/common/objects';
import { basename, dirname, join, sep } from 'vs/base/common/path';
import { StopWatch } from 'vs/base/common/stopwatch';
import * as strings from 'vs/base/common/strings';
import { URI, UriComponents } from 'vs/base/common/uri';
import { compareItemsByFuzzyScore, IItemAccessor, prepareQuery, FuzzyScorerCache } from 'vs/base/common/fuzzyScorer';
import { MAX_FILE_SIZE } from 'vs/base/node/pfs';
import { ICachedSearchStats, IFileQuery, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileQuery, IRawQuery, IRawTextQuery, ITextQuery, IFileSearchProgressItem, IRawFileMatch, IRawSearchService, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFilePatternMatch } from 'vs/workbench/services/search/common/search';
import { ICachedSearchStats, IFileQuery, IFileSearchProgressItem, IFileSearchStats, IFolderQuery, IProgressMessage, IRawFileMatch, IRawFileQuery, IRawQuery, IRawSearchService, IRawTextQuery, ISearchEngine, ISearchEngineSuccess, ISerializedFileMatch, ISerializedSearchComplete, ISerializedSearchProgressItem, ISerializedSearchSuccess, isFilePatternMatch, ITextQuery } from 'vs/workbench/services/search/common/search';
import { Engine as FileSearchEngine } from 'vs/workbench/services/search/node/fileSearch';
import { TextSearchEngineAdapter } from 'vs/workbench/services/search/node/textSearchAdapter';
@@ -276,7 +275,7 @@ export class SearchService implements IRawSearchService {
let cachedRow: ICacheRow | undefined;
for (const previousSearch in cache.resultsToSearchCache) {
// If we narrow down, we might be able to reuse the cached results
if (strings.startsWith(searchValue, previousSearch)) {
if (searchValue.startsWith(previousSearch)) {
if (hasPathSep && previousSearch.indexOf(sep) < 0 && previousSearch !== '') {
continue; // since a path character widens the search for potential more matches, require it in previous search too
}

View File

@@ -131,14 +131,14 @@ function globExprsToRgGlobs(patterns: glob.IExpression, folder?: string, exclude
// glob.ts requires forward slashes, but a UNC path still must start with \\
// #38165 and #38151
if (strings.startsWith(key, '\\\\')) {
if (key.startsWith('\\\\')) {
key = '\\\\' + key.substr(2).replace(/\\/g, '/');
} else {
key = key.replace(/\\/g, '/');
}
if (typeof value === 'boolean' && value) {
if (strings.startsWith(key, '\\\\')) {
if (key.startsWith('\\\\')) {
// Absolute globs UNC paths don't work properly, see #58758
key += '**';
}

View File

@@ -3,17 +3,16 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { startsWith } from 'vs/base/common/strings';
import { ILogService } from 'vs/platform/log/common/log';
import { SearchRange, TextSearchMatch } from 'vs/workbench/services/search/common/search';
import { mapArrayOrNot } from 'vs/base/common/arrays';
import { URI } from 'vs/base/common/uri';
import { ILogService } from 'vs/platform/log/common/log';
import { SearchRange, TextSearchMatch } from 'vs/workbench/services/search/common/search';
import * as searchExtTypes from 'vs/workbench/services/search/common/searchExtTypes';
export type Maybe<T> = T | null | undefined;
export function anchorGlob(glob: string): string {
return startsWith(glob, '**') || startsWith(glob, '/') ? glob : `/${glob}`;
return glob.startsWith('**') || glob.startsWith('/') ? glob : `/${glob}`;
}
/**

View File

@@ -5,19 +5,19 @@
import * as cp from 'child_process';
import { EventEmitter } from 'events';
import * as path from 'vs/base/common/path';
import { StringDecoder } from 'string_decoder';
import { createRegExp, startsWith, startsWithUTF8BOM, stripUTF8BOM, escapeRegExpCharacters, endsWith } from 'vs/base/common/strings';
import { coalesce } from 'vs/base/common/arrays';
import { CancellationToken } from 'vs/base/common/cancellation';
import { groupBy } from 'vs/base/common/collections';
import { splitGlobAware } from 'vs/base/common/glob';
import * as path from 'vs/base/common/path';
import { createRegExp, escapeRegExpCharacters, startsWithUTF8BOM, stripUTF8BOM } from 'vs/base/common/strings';
import { URI } from 'vs/base/common/uri';
import { Progress } from 'vs/platform/progress/common/progress';
import { IExtendedExtensionSearchOptions, SearchError, SearchErrorCode, serializeSearchError } from 'vs/workbench/services/search/common/search';
import { Range, TextSearchComplete, TextSearchContext, TextSearchMatch, TextSearchOptions, TextSearchPreviewOptions, TextSearchQuery, TextSearchResult } from 'vs/workbench/services/search/common/searchExtTypes';
import { rgPath } from 'vscode-ripgrep';
import { anchorGlob, createTextSearchResult, IOutputChannel, Maybe } from './ripgrepSearchUtils';
import { coalesce } from 'vs/base/common/arrays';
import { splitGlobAware } from 'vs/base/common/glob';
import { groupBy } from 'vs/base/common/collections';
import { TextSearchQuery, TextSearchOptions, TextSearchResult, TextSearchComplete, TextSearchPreviewOptions, TextSearchContext, TextSearchMatch, Range } from 'vs/workbench/services/search/common/searchExtTypes';
import { Progress } from 'vs/platform/progress/common/progress';
import { CancellationToken } from 'vs/base/common/cancellation';
// If vscode-ripgrep is in an .asar file, then the binary is unpacked.
const rgDiskPath = rgPath.replace(/\bnode_modules\.asar\b/, 'node_modules.asar.unpacked');
@@ -125,7 +125,7 @@ export function rgErrorMsgForDisplay(msg: string): Maybe<SearchError> {
const lines = msg.split('\n');
const firstLine = lines[0].trim();
if (lines.some(l => startsWith(l, 'regex parse error'))) {
if (lines.some(l => l.startsWith('regex parse error'))) {
return new SearchError(buildRegexParseError(lines), SearchErrorCode.regexParseError);
}
@@ -134,17 +134,17 @@ export function rgErrorMsgForDisplay(msg: string): Maybe<SearchError> {
return new SearchError(`Unknown encoding: ${match[1]}`, SearchErrorCode.unknownEncoding);
}
if (startsWith(firstLine, 'error parsing glob')) {
if (firstLine.startsWith('error parsing glob')) {
// Uppercase first letter
return new SearchError(firstLine.charAt(0).toUpperCase() + firstLine.substr(1), SearchErrorCode.globParseError);
}
if (startsWith(firstLine, 'the literal')) {
if (firstLine.startsWith('the literal')) {
// Uppercase first letter
return new SearchError(firstLine.charAt(0).toUpperCase() + firstLine.substr(1), SearchErrorCode.invalidLiteral);
}
if (startsWith(firstLine, 'PCRE2: error compiling pattern')) {
if (firstLine.startsWith('PCRE2: error compiling pattern')) {
return new SearchError(firstLine, SearchErrorCode.regexParseError);
}
@@ -153,7 +153,7 @@ export function rgErrorMsgForDisplay(msg: string): Maybe<SearchError> {
export function buildRegexParseError(lines: string[]): string {
let errorMessage: string[] = ['Regex parse error'];
let pcre2ErrorLine = lines.filter(l => (startsWith(l, 'PCRE2:')));
let pcre2ErrorLine = lines.filter(l => (l.startsWith('PCRE2:')));
if (pcre2ErrorLine.length >= 1) {
let pcre2ErrorMessage = pcre2ErrorLine[0].replace('PCRE2:', '');
if (pcre2ErrorMessage.indexOf(':') !== -1 && pcre2ErrorMessage.split(':').length >= 2) {
@@ -358,12 +358,12 @@ function getRgArgs(query: TextSearchQuery, options: TextSearchOptions): string[]
const { doubleStarIncludes, otherIncludes } = groupBy(
options.includes,
(include: string) => startsWith(include, '**') ? 'doubleStarIncludes' : 'otherIncludes');
(include: string) => include.startsWith('**') ? 'doubleStarIncludes' : 'otherIncludes');
if (otherIncludes && otherIncludes.length) {
const uniqueOthers = new Set<string>();
otherIncludes.forEach(other => {
if (!endsWith(other, '/**')) {
if (!other.endsWith('/**')) {
other += '/**';
}

View File

@@ -53,7 +53,7 @@ export class DiskSearch implements ISearchResultProvider {
@IConfigurationService private readonly configService: IConfigurationService,
) {
const timeout = this.configService.getValue<ISearchConfiguration>().search.maintainFileSearchCache ?
Number.MAX_VALUE :
100 * 60 * 60 * 1000 :
60 * 60 * 1000;
const opts: IIPCOptions = {

View File

@@ -33,6 +33,12 @@ export interface IStatusbarEntry {
*/
readonly ariaLabel: string;
/**
* Role of the status bar entry which defines how a screen reader interacts with it.
* Default is 'button'.
*/
readonly role?: string;
/**
* An optional tooltip text to show when you hover over the entry
*/

View File

@@ -3,23 +3,13 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { AbstractTextFileService, EncodingOracle } from 'vs/workbench/services/textfile/browser/textFileService';
import { ITextFileService, IResourceEncoding, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles';
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
import { ITextFileService, TextFileEditorModelState } from 'vs/workbench/services/textfile/common/textfiles';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { ShutdownReason } from 'vs/platform/lifecycle/common/lifecycle';
export class BrowserTextFileService extends AbstractTextFileService {
private _browserEncoding: EncodingOracle | undefined;
get encoding(): EncodingOracle {
if (!this._browserEncoding) {
this._browserEncoding = this._register(this.instantiationService.createInstance(BrowserEncodingOracle));
}
return this._browserEncoding;
}
protected registerListeners(): void {
super.registerListeners();
@@ -38,18 +28,4 @@ export class BrowserTextFileService extends AbstractTextFileService {
}
}
class BrowserEncodingOracle extends EncodingOracle {
async getPreferredWriteEncoding(): Promise<IResourceEncoding> {
return { encoding: 'utf8', hasBOM: false };
}
async getWriteEncoding(): Promise<{ encoding: string, addBOM: boolean }> {
return { encoding: 'utf8', addBOM: false };
}
async getReadEncoding(): Promise<string> {
return 'utf8';
}
}
registerSingleton(ITextFileService, BrowserTextFileService);

View File

@@ -5,10 +5,10 @@
import * as nls from 'vs/nls';
import { URI } from 'vs/base/common/uri';
import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions, ITextFileEditorModelManager, IResourceEncoding } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, ITextFileStreamContent, ITextFileContent, IResourceEncodings, IReadTextFileOptions, IWriteTextFileOptions, toBufferOrReadable, TextFileOperationError, TextFileOperationResult, ITextFileSaveOptions, ITextFileEditorModelManager, IResourceEncoding, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
import { IRevertOptions, IEncodingSupport } from 'vs/workbench/common/editor';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions } from 'vs/platform/files/common/files';
import { IFileService, FileOperationError, FileOperationResult, IFileStatWithMetadata, ICreateFileOptions, IFileStreamContent } from 'vs/platform/files/common/files';
import { Disposable } from 'vs/base/common/lifecycle';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IUntitledTextEditorService, IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
@@ -18,9 +18,9 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { Schemas } from 'vs/base/common/network';
import { createTextBufferFactoryFromSnapshot, createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
import { IModelService } from 'vs/editor/common/services/modelService';
import { joinPath, dirname, basename, toLocalResource, extUri, extname, isEqualOrParent } from 'vs/base/common/resources';
import { joinPath, dirname, basename, toLocalResource, extUri, extname } from 'vs/base/common/resources';
import { IDialogService, IFileDialogService, IConfirmation } from 'vs/platform/dialogs/common/dialogs';
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
import { VSBuffer, VSBufferReadable, bufferToStream } from 'vs/base/common/buffer';
import { ITextSnapshot, ITextModel } from 'vs/editor/common/model';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
@@ -28,7 +28,6 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura
import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/services/resolverService';
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
import { suggestFilename } from 'vs/base/common/mime';
import { IPathService } from 'vs/workbench/services/path/common/pathService';
import { isValidBasename } from 'vs/base/common/extpath';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
@@ -36,7 +35,9 @@ import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/ur
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, UTF8_BOM, detectEncodingByBOMFromBuffer } from 'vs/workbench/services/textfile/common/encoding';
import { UTF8, UTF8_with_bom, UTF16be, UTF16le, encodingExists, UTF8_BOM, detectEncodingByBOMFromBuffer, toEncodeReadable, toDecodeStream, IDecodeStreamResult } from 'vs/workbench/services/textfile/common/encoding';
import { consumeStream } from 'vs/base/common/stream';
import { IModeService } from 'vs/editor/common/services/modeService';
/**
* The workbench file service implementation implements the raw file service spec and adds additional methods on top.
@@ -64,7 +65,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
@ICodeEditorService private readonly codeEditorService: ICodeEditorService,
@IPathService private readonly pathService: IPathService,
@IWorkingCopyFileService private readonly workingCopyFileService: IWorkingCopyFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService,
@IModeService private readonly modeService: IModeService
) {
super();
@@ -90,75 +92,91 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
}
async read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
const content = await this.fileService.readFile(resource, options);
// in case of acceptTextOnly: true, we check the first
// chunk for possibly being binary by looking for 0-bytes
// we limit this check to the first 512 bytes
this.validateBinary(content.value, options);
const [bufferStream, decoder] = await this.doRead(resource, {
...options,
// optimization: since we know that the caller does not
// care about buffering, we indicate this to the reader.
// this reduces all the overhead the buffered reading
// has (open, read, close) if the provider supports
// unbuffered reading.
preferUnbuffered: true
});
return {
...content,
encoding: 'utf8',
value: content.value.toString()
...bufferStream,
encoding: decoder.detected.encoding || UTF8,
value: await consumeStream(decoder.stream, strings => strings.join(''))
};
}
async readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent> {
const stream = await this.fileService.readFileStream(resource, options);
// in case of acceptTextOnly: true, we check the first
// chunk for possibly being binary by looking for 0-bytes
// we limit this check to the first 512 bytes
let checkedForBinary = false;
const throwOnBinary = (data: VSBuffer): Error | undefined => {
if (!checkedForBinary) {
checkedForBinary = true;
this.validateBinary(data, options);
}
return undefined;
};
const [bufferStream, decoder] = await this.doRead(resource, options);
return {
...stream,
encoding: 'utf8',
value: await createTextBufferFactoryFromStream(stream.value, undefined, options?.acceptTextOnly ? throwOnBinary : undefined)
...bufferStream,
encoding: decoder.detected.encoding || UTF8,
value: await createTextBufferFactoryFromStream(decoder.stream)
};
}
private validateBinary(buffer: VSBuffer, options?: IReadTextFileOptions): void {
if (!options || !options.acceptTextOnly) {
return; // no validation needed
private async doRead(resource: URI, options?: IReadTextFileOptions & { preferUnbuffered?: boolean }): Promise<[IFileStreamContent, IDecodeStreamResult]> {
// read stream raw (either buffered or unbuffered)
let bufferStream: IFileStreamContent;
if (options?.preferUnbuffered) {
const content = await this.fileService.readFile(resource, options);
bufferStream = {
...content,
value: bufferToStream(content.value)
};
} else {
bufferStream = await this.fileService.readFileStream(resource, options);
}
// in case of acceptTextOnly: true, we check the first
// chunk for possibly being binary by looking for 0-bytes
// we limit this check to the first 512 bytes
for (let i = 0; i < buffer.byteLength && i < 512; i++) {
if (buffer.readUInt8(i) === 0) {
throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options);
}
// read through encoding library
const decoder = await toDecodeStream(bufferStream.value, {
guessEncoding: options?.autoGuessEncoding || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'),
overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding)
});
// validate binary
if (options?.acceptTextOnly && decoder.detected.seemsBinary) {
throw new TextFileOperationError(nls.localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options);
}
return [bufferStream, decoder];
}
async create(resource: URI, value?: string | ITextSnapshot, options?: ICreateFileOptions): Promise<IFileStatWithMetadata> {
const encodedValue = await this.doEncodeText(resource, value);
const readable = await this.getEncodedReadable(resource, value);
return this.workingCopyFileService.create(resource, encodedValue, options);
}
protected async doEncodeText(resource: URI, value?: string | ITextSnapshot): Promise<VSBuffer | VSBufferReadable | undefined> {
if (!value) {
return undefined;
}
return toBufferOrReadable(value);
return this.workingCopyFileService.create(resource, readable, options);
}
async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
return this.fileService.writeFile(resource, toBufferOrReadable(value), options);
const readable = await this.getEncodedReadable(resource, value, options);
return this.fileService.writeFile(resource, readable, options);
}
private async getEncodedReadable(resource: URI, value?: string | ITextSnapshot): Promise<VSBuffer | VSBufferReadable | undefined>;
private async getEncodedReadable(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<VSBuffer | VSBufferReadable>;
private async getEncodedReadable(resource: URI, value?: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<VSBuffer | VSBufferReadable | undefined> {
// check for encoding
const { encoding, addBOM } = await this.encoding.getWriteEncoding(resource, options);
// when encoding is standard skip encoding step
if (encoding === UTF8 && !addBOM) {
return typeof value === 'undefined'
? undefined
: toBufferOrReadable(value);
}
// otherwise create encoded readable
value = value || '';
const snapshot = typeof value === 'string' ? stringToSnapshot(value) : value;
return toEncodeReadable(snapshot, encoding, { addBOM });
}
//#endregion
@@ -425,8 +443,8 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
// Add mode file extension if specified
const mode = model.getMode();
if (mode !== PLAINTEXT_MODE_ID) {
suggestedFilename = suggestFilename(mode, untitledName);
if (mode && mode !== PLAINTEXT_MODE_ID) {
suggestedFilename = this.suggestFilename(mode, untitledName);
} else {
suggestedFilename = untitledName;
}
@@ -443,6 +461,17 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
return joinPath(this.fileDialogService.defaultFilePath() || (await this.pathService.userHome()), suggestedFilename);
}
suggestFilename(mode: string, untitledName: string) {
const extension = this.modeService.getExtensions(mode)[0];
if (extension) {
if (!untitledName.endsWith(extension)) {
return untitledName + extension;
}
}
const filename = this.modeService.getFilenames(mode)[0];
return filename || untitledName;
}
//#endregion
//#region revert
@@ -498,7 +527,8 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
@ITextResourceConfigurationService private textResourceConfigurationService: ITextResourceConfigurationService,
@IEnvironmentService private environmentService: IEnvironmentService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
@IFileService private fileService: IFileService
@IFileService private fileService: IFileService,
@IUriIdentityService private readonly uriIdentityService: IUriIdentityService
) {
super();
@@ -602,8 +632,10 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
fileEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding'); // and last we check for settings
}
if (!fileEncoding || !(await encodingExists(fileEncoding))) {
fileEncoding = UTF8; // the default is UTF 8
if (fileEncoding !== UTF8) {
if (!fileEncoding || !(await encodingExists(fileEncoding))) {
fileEncoding = UTF8; // the default is UTF-8
}
}
return fileEncoding;
@@ -614,7 +646,7 @@ export class EncodingOracle extends Disposable implements IResourceEncodings {
for (const override of this.encodingOverrides) {
// check if the resource is child of encoding override path
if (override.parent && isEqualOrParent(resource, override.parent)) {
if (override.parent && this.uriIdentityService.extUri.isEqualOrParent(resource, override.parent)) {
return override.encoding;
}

View File

@@ -3,7 +3,6 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { DecoderStream } from 'iconv-lite-umd';
import { Readable, ReadableStream, newWriteableStream } from 'vs/base/common/stream';
import { VSBuffer, VSBufferReadable, VSBufferReadableStream } from 'vs/base/common/buffer';
@@ -39,6 +38,60 @@ export interface IDecodeStreamResult {
detected: IDetectedEncodingResult;
}
export interface IDecoderStream {
write(buffer: Uint8Array): string;
end(): string | undefined;
}
class DecoderStream implements IDecoderStream {
/**
* This stream will only load iconv-lite lazily if the encoding
* is not UTF-8. This ensures that for most common cases we do
* not pay the price of loading the module from disk.
*
* We still need to be careful when converting UTF-8 to a string
* though because we read the file in chunks of Buffer and thus
* need to decode it via TextDecoder helper that is available
* in browser and node.js environments.
*/
static async create(encoding: string): Promise<DecoderStream> {
let decoder: IDecoderStream | undefined = undefined;
if (encoding !== UTF8) {
const iconv = await import('iconv-lite-umd');
decoder = iconv.getDecoder(toNodeEncoding(encoding));
} else {
const utf8TextDecoder = new TextDecoder();
decoder = {
write(buffer: Uint8Array): string {
return utf8TextDecoder.decode(buffer, {
// Signal to TextDecoder that potentially more data is coming
// and that we are calling `decode` in the end to consume any
// remainders
stream: true
});
},
end(): string | undefined {
return utf8TextDecoder.decode();
}
};
}
return new DecoderStream(decoder);
}
private constructor(private iconvLiteDecoder: IDecoderStream) { }
write(buffer: Uint8Array): string {
return this.iconvLiteDecoder.write(buffer);
}
end(): string | undefined {
return this.iconvLiteDecoder.end();
}
}
export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeStreamOptions): Promise<IDecodeStreamResult> {
const minBytesRequiredForDetection = options.minBytesRequiredForDetection ?? options.guessEncoding ? AUTO_ENCODING_GUESS_MIN_BYTES : NO_ENCODING_GUESS_MIN_BYTES;
@@ -48,7 +101,7 @@ export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeS
const bufferedChunks: VSBuffer[] = [];
let bytesBuffered = 0;
let decoder: DecoderStream | undefined = undefined;
let decoder: IDecoderStream | undefined = undefined;
const createDecoder = async () => {
try {
@@ -63,8 +116,7 @@ export function toDecodeStream(source: VSBufferReadableStream, options: IDecodeS
detected.encoding = await options.overwriteEncoding(detected.encoding);
// decode and write buffered content
const iconv = await import('iconv-lite-umd');
decoder = iconv.getDecoder(toNodeEncoding(detected.encoding));
decoder = await DecoderStream.create(detected.encoding);
const decoded = decoder.write(VSBuffer.concat(bufferedChunks).buffer);
target.write(decoded);
@@ -132,7 +184,7 @@ export async function toEncodeReadable(readable: Readable<string>, encoding: str
const iconv = await import('iconv-lite-umd');
const encoder = iconv.getEncoder(toNodeEncoding(encoding), options);
let bytesRead = 0;
let bytesWritten = false;
let done = false;
return {
@@ -146,9 +198,9 @@ export async function toEncodeReadable(readable: Readable<string>, encoding: str
done = true;
// If we are instructed to add a BOM but we detect that no
// bytes have been read, we must ensure to return the BOM
// bytes have been written, we must ensure to return the BOM
// ourselves so that we comply with the contract.
if (bytesRead === 0 && options?.addBOM) {
if (!bytesWritten && options?.addBOM) {
switch (encoding) {
case UTF8:
case UTF8_with_bom:
@@ -162,13 +214,15 @@ export async function toEncodeReadable(readable: Readable<string>, encoding: str
const leftovers = encoder.end();
if (leftovers && leftovers.length > 0) {
bytesWritten = true;
return VSBuffer.wrap(leftovers);
}
return null;
}
bytesRead += chunk.length;
bytesWritten = true;
return VSBuffer.wrap(encoder.write(chunk));
}
@@ -236,11 +290,13 @@ async function guessEncodingByBuffer(buffer: VSBuffer): Promise<string | null> {
// ensure to limit buffer for guessing due to https://github.com/aadsm/jschardet/issues/53
const limitedBuffer = buffer.slice(0, AUTO_ENCODING_GUESS_MAX_BYTES);
// override type since jschardet expects Buffer even though can accept Uint8Array
// can be fixed once https://github.com/aadsm/jschardet/pull/58 is merged
const jschardetTypingsWorkaround = limitedBuffer.buffer as any;
const guessed = jschardet.detect(jschardetTypingsWorkaround);
// before guessing jschardet calls toString('binary') on input if it is a Buffer,
// since we are using it inside browser environment as well we do conversion ourselves
// https://github.com/aadsm/jschardet/blob/v2.1.1/src/index.js#L36-L40
const binaryString = encodeLatin1(limitedBuffer.buffer);
const guessed = jschardet.detect(binaryString);
if (!guessed || !guessed.encoding) {
return null;
}
@@ -265,6 +321,15 @@ function toIconvLiteEncoding(encodingName: string): string {
return mapped || normalizedEncodingName;
}
function encodeLatin1(buffer: Uint8Array): string {
let result = '';
for (let i = 0; i < buffer.length; i++) {
result += String.fromCharCode(buffer[i]);
}
return result;
}
/**
* The encodings that are allowed in a settings file don't match the canonical encoding labels specified by WHATWG.
* See https://encoding.spec.whatwg.org/#names-and-labels

View File

@@ -694,15 +694,15 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
// participant triggering
this.logService.trace(`[text file model] doSave(${versionId}) - before write()`, this.resource.toString(true));
const lastResolvedFileStat = assertIsDefined(this.lastResolvedFileStat);
const textFileEdiorModel = this;
const textFileEditorModel = this;
return this.saveSequentializer.setPending(versionId, (async () => {
try {
const stat = await this.textFileService.write(lastResolvedFileStat.resource, textFileEdiorModel.createSnapshot(), {
const stat = await this.textFileService.write(lastResolvedFileStat.resource, textFileEditorModel.createSnapshot(), {
overwriteReadonly: options.overwriteReadonly,
overwriteEncoding: options.overwriteEncoding,
mtime: lastResolvedFileStat.mtime,
encoding: this.getEncoding(),
etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, textFileEdiorModel.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag,
etag: (options.ignoreModifiedSince || !this.filesConfigurationService.preventSaveConflicts(lastResolvedFileStat.resource, textFileEditorModel.getMode())) ? ETAG_DISABLED : lastResolvedFileStat.etag,
writeElevated: options.writeElevated
});

View File

@@ -13,7 +13,6 @@ import { ITextEditorModel } from 'vs/editor/common/services/resolverService';
import { ITextBufferFactory, ITextModel, ITextSnapshot } from 'vs/editor/common/model';
import { VSBuffer, VSBufferReadable } from 'vs/base/common/buffer';
import { isUndefinedOrNull } from 'vs/base/common/types';
import { isNative } from 'vs/base/common/platform';
import { IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
import { IUntitledTextEditorModelManager } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -410,6 +409,7 @@ export interface ITextFileEditorModel extends ITextEditorModel, IEncodingSupport
readonly onDidChangeContent: Event<void>;
readonly onDidSaveError: Event<void>;
readonly onDidChangeOrphaned: Event<void>;
readonly onDidChangeEncoding: Event<void>;
hasState(state: TextFileEditorModelState): boolean;
@@ -507,257 +507,243 @@ export function toBufferOrReadable(value: string | ITextSnapshot | undefined): V
return new TextSnapshotReadable(value);
}
export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } =
// Desktop
isNative ?
{
utf8: {
labelLong: 'UTF-8',
labelShort: 'UTF-8',
order: 1,
alias: 'utf8bom'
},
utf8bom: {
labelLong: 'UTF-8 with BOM',
labelShort: 'UTF-8 with BOM',
encodeOnly: true,
order: 2,
alias: 'utf8'
},
utf16le: {
labelLong: 'UTF-16 LE',
labelShort: 'UTF-16 LE',
order: 3
},
utf16be: {
labelLong: 'UTF-16 BE',
labelShort: 'UTF-16 BE',
order: 4
},
windows1252: {
labelLong: 'Western (Windows 1252)',
labelShort: 'Windows 1252',
order: 5
},
iso88591: {
labelLong: 'Western (ISO 8859-1)',
labelShort: 'ISO 8859-1',
order: 6
},
iso88593: {
labelLong: 'Western (ISO 8859-3)',
labelShort: 'ISO 8859-3',
order: 7
},
iso885915: {
labelLong: 'Western (ISO 8859-15)',
labelShort: 'ISO 8859-15',
order: 8
},
macroman: {
labelLong: 'Western (Mac Roman)',
labelShort: 'Mac Roman',
order: 9
},
cp437: {
labelLong: 'DOS (CP 437)',
labelShort: 'CP437',
order: 10
},
windows1256: {
labelLong: 'Arabic (Windows 1256)',
labelShort: 'Windows 1256',
order: 11
},
iso88596: {
labelLong: 'Arabic (ISO 8859-6)',
labelShort: 'ISO 8859-6',
order: 12
},
windows1257: {
labelLong: 'Baltic (Windows 1257)',
labelShort: 'Windows 1257',
order: 13
},
iso88594: {
labelLong: 'Baltic (ISO 8859-4)',
labelShort: 'ISO 8859-4',
order: 14
},
iso885914: {
labelLong: 'Celtic (ISO 8859-14)',
labelShort: 'ISO 8859-14',
order: 15
},
windows1250: {
labelLong: 'Central European (Windows 1250)',
labelShort: 'Windows 1250',
order: 16
},
iso88592: {
labelLong: 'Central European (ISO 8859-2)',
labelShort: 'ISO 8859-2',
order: 17
},
cp852: {
labelLong: 'Central European (CP 852)',
labelShort: 'CP 852',
order: 18
},
windows1251: {
labelLong: 'Cyrillic (Windows 1251)',
labelShort: 'Windows 1251',
order: 19
},
cp866: {
labelLong: 'Cyrillic (CP 866)',
labelShort: 'CP 866',
order: 20
},
iso88595: {
labelLong: 'Cyrillic (ISO 8859-5)',
labelShort: 'ISO 8859-5',
order: 21
},
koi8r: {
labelLong: 'Cyrillic (KOI8-R)',
labelShort: 'KOI8-R',
order: 22
},
koi8u: {
labelLong: 'Cyrillic (KOI8-U)',
labelShort: 'KOI8-U',
order: 23
},
iso885913: {
labelLong: 'Estonian (ISO 8859-13)',
labelShort: 'ISO 8859-13',
order: 24
},
windows1253: {
labelLong: 'Greek (Windows 1253)',
labelShort: 'Windows 1253',
order: 25
},
iso88597: {
labelLong: 'Greek (ISO 8859-7)',
labelShort: 'ISO 8859-7',
order: 26
},
windows1255: {
labelLong: 'Hebrew (Windows 1255)',
labelShort: 'Windows 1255',
order: 27
},
iso88598: {
labelLong: 'Hebrew (ISO 8859-8)',
labelShort: 'ISO 8859-8',
order: 28
},
iso885910: {
labelLong: 'Nordic (ISO 8859-10)',
labelShort: 'ISO 8859-10',
order: 29
},
iso885916: {
labelLong: 'Romanian (ISO 8859-16)',
labelShort: 'ISO 8859-16',
order: 30
},
windows1254: {
labelLong: 'Turkish (Windows 1254)',
labelShort: 'Windows 1254',
order: 31
},
iso88599: {
labelLong: 'Turkish (ISO 8859-9)',
labelShort: 'ISO 8859-9',
order: 32
},
windows1258: {
labelLong: 'Vietnamese (Windows 1258)',
labelShort: 'Windows 1258',
order: 33
},
gbk: {
labelLong: 'Simplified Chinese (GBK)',
labelShort: 'GBK',
order: 34
},
gb18030: {
labelLong: 'Simplified Chinese (GB18030)',
labelShort: 'GB18030',
order: 35
},
cp950: {
labelLong: 'Traditional Chinese (Big5)',
labelShort: 'Big5',
order: 36
},
big5hkscs: {
labelLong: 'Traditional Chinese (Big5-HKSCS)',
labelShort: 'Big5-HKSCS',
order: 37
},
shiftjis: {
labelLong: 'Japanese (Shift JIS)',
labelShort: 'Shift JIS',
order: 38
},
eucjp: {
labelLong: 'Japanese (EUC-JP)',
labelShort: 'EUC-JP',
order: 39
},
euckr: {
labelLong: 'Korean (EUC-KR)',
labelShort: 'EUC-KR',
order: 40
},
windows874: {
labelLong: 'Thai (Windows 874)',
labelShort: 'Windows 874',
order: 41
},
iso885911: {
labelLong: 'Latin/Thai (ISO 8859-11)',
labelShort: 'ISO 8859-11',
order: 42
},
koi8ru: {
labelLong: 'Cyrillic (KOI8-RU)',
labelShort: 'KOI8-RU',
order: 43
},
koi8t: {
labelLong: 'Tajik (KOI8-T)',
labelShort: 'KOI8-T',
order: 44
},
gb2312: {
labelLong: 'Simplified Chinese (GB 2312)',
labelShort: 'GB 2312',
order: 45
},
cp865: {
labelLong: 'Nordic DOS (CP 865)',
labelShort: 'CP 865',
order: 46
},
cp850: {
labelLong: 'Western European DOS (CP 850)',
labelShort: 'CP 850',
order: 47
}
} :
// Web (https://github.com/microsoft/vscode/issues/79275)
{
utf8: {
labelLong: 'UTF-8',
labelShort: 'UTF-8',
order: 1,
alias: 'utf8bom'
}
};
export const SUPPORTED_ENCODINGS: { [encoding: string]: { labelLong: string; labelShort: string; order: number; encodeOnly?: boolean; alias?: string } } = {
utf8: {
labelLong: 'UTF-8',
labelShort: 'UTF-8',
order: 1,
alias: 'utf8bom'
},
utf8bom: {
labelLong: 'UTF-8 with BOM',
labelShort: 'UTF-8 with BOM',
encodeOnly: true,
order: 2,
alias: 'utf8'
},
utf16le: {
labelLong: 'UTF-16 LE',
labelShort: 'UTF-16 LE',
order: 3
},
utf16be: {
labelLong: 'UTF-16 BE',
labelShort: 'UTF-16 BE',
order: 4
},
windows1252: {
labelLong: 'Western (Windows 1252)',
labelShort: 'Windows 1252',
order: 5
},
iso88591: {
labelLong: 'Western (ISO 8859-1)',
labelShort: 'ISO 8859-1',
order: 6
},
iso88593: {
labelLong: 'Western (ISO 8859-3)',
labelShort: 'ISO 8859-3',
order: 7
},
iso885915: {
labelLong: 'Western (ISO 8859-15)',
labelShort: 'ISO 8859-15',
order: 8
},
macroman: {
labelLong: 'Western (Mac Roman)',
labelShort: 'Mac Roman',
order: 9
},
cp437: {
labelLong: 'DOS (CP 437)',
labelShort: 'CP437',
order: 10
},
windows1256: {
labelLong: 'Arabic (Windows 1256)',
labelShort: 'Windows 1256',
order: 11
},
iso88596: {
labelLong: 'Arabic (ISO 8859-6)',
labelShort: 'ISO 8859-6',
order: 12
},
windows1257: {
labelLong: 'Baltic (Windows 1257)',
labelShort: 'Windows 1257',
order: 13
},
iso88594: {
labelLong: 'Baltic (ISO 8859-4)',
labelShort: 'ISO 8859-4',
order: 14
},
iso885914: {
labelLong: 'Celtic (ISO 8859-14)',
labelShort: 'ISO 8859-14',
order: 15
},
windows1250: {
labelLong: 'Central European (Windows 1250)',
labelShort: 'Windows 1250',
order: 16
},
iso88592: {
labelLong: 'Central European (ISO 8859-2)',
labelShort: 'ISO 8859-2',
order: 17
},
cp852: {
labelLong: 'Central European (CP 852)',
labelShort: 'CP 852',
order: 18
},
windows1251: {
labelLong: 'Cyrillic (Windows 1251)',
labelShort: 'Windows 1251',
order: 19
},
cp866: {
labelLong: 'Cyrillic (CP 866)',
labelShort: 'CP 866',
order: 20
},
iso88595: {
labelLong: 'Cyrillic (ISO 8859-5)',
labelShort: 'ISO 8859-5',
order: 21
},
koi8r: {
labelLong: 'Cyrillic (KOI8-R)',
labelShort: 'KOI8-R',
order: 22
},
koi8u: {
labelLong: 'Cyrillic (KOI8-U)',
labelShort: 'KOI8-U',
order: 23
},
iso885913: {
labelLong: 'Estonian (ISO 8859-13)',
labelShort: 'ISO 8859-13',
order: 24
},
windows1253: {
labelLong: 'Greek (Windows 1253)',
labelShort: 'Windows 1253',
order: 25
},
iso88597: {
labelLong: 'Greek (ISO 8859-7)',
labelShort: 'ISO 8859-7',
order: 26
},
windows1255: {
labelLong: 'Hebrew (Windows 1255)',
labelShort: 'Windows 1255',
order: 27
},
iso88598: {
labelLong: 'Hebrew (ISO 8859-8)',
labelShort: 'ISO 8859-8',
order: 28
},
iso885910: {
labelLong: 'Nordic (ISO 8859-10)',
labelShort: 'ISO 8859-10',
order: 29
},
iso885916: {
labelLong: 'Romanian (ISO 8859-16)',
labelShort: 'ISO 8859-16',
order: 30
},
windows1254: {
labelLong: 'Turkish (Windows 1254)',
labelShort: 'Windows 1254',
order: 31
},
iso88599: {
labelLong: 'Turkish (ISO 8859-9)',
labelShort: 'ISO 8859-9',
order: 32
},
windows1258: {
labelLong: 'Vietnamese (Windows 1258)',
labelShort: 'Windows 1258',
order: 33
},
gbk: {
labelLong: 'Simplified Chinese (GBK)',
labelShort: 'GBK',
order: 34
},
gb18030: {
labelLong: 'Simplified Chinese (GB18030)',
labelShort: 'GB18030',
order: 35
},
cp950: {
labelLong: 'Traditional Chinese (Big5)',
labelShort: 'Big5',
order: 36
},
big5hkscs: {
labelLong: 'Traditional Chinese (Big5-HKSCS)',
labelShort: 'Big5-HKSCS',
order: 37
},
shiftjis: {
labelLong: 'Japanese (Shift JIS)',
labelShort: 'Shift JIS',
order: 38
},
eucjp: {
labelLong: 'Japanese (EUC-JP)',
labelShort: 'EUC-JP',
order: 39
},
euckr: {
labelLong: 'Korean (EUC-KR)',
labelShort: 'EUC-KR',
order: 40
},
windows874: {
labelLong: 'Thai (Windows 874)',
labelShort: 'Windows 874',
order: 41
},
iso885911: {
labelLong: 'Latin/Thai (ISO 8859-11)',
labelShort: 'ISO 8859-11',
order: 42
},
koi8ru: {
labelLong: 'Cyrillic (KOI8-RU)',
labelShort: 'KOI8-RU',
order: 43
},
koi8t: {
labelLong: 'Tajik (KOI8-T)',
labelShort: 'KOI8-T',
order: 44
},
gb2312: {
labelLong: 'Simplified Chinese (GB 2312)',
labelShort: 'GB 2312',
order: 45
},
cp865: {
labelLong: 'Nordic DOS (CP 865)',
labelShort: 'CP 865',
order: 46
},
cp850: {
labelLong: 'Western European DOS (CP 850)',
labelShort: 'CP 850',
order: 47
}
};

View File

@@ -5,21 +5,18 @@
import { localize } from 'vs/nls';
import { AbstractTextFileService } from 'vs/workbench/services/textfile/browser/textFileService';
import { ITextFileService, ITextFileStreamContent, ITextFileContent, IReadTextFileOptions, IWriteTextFileOptions, stringToSnapshot, TextFileOperationResult, TextFileOperationError } from 'vs/workbench/services/textfile/common/textfiles';
import { ITextFileService, ITextFileStreamContent, ITextFileContent, IReadTextFileOptions, IWriteTextFileOptions } from 'vs/workbench/services/textfile/common/textfiles';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { URI } from 'vs/base/common/uri';
import { IFileStatWithMetadata, FileOperationError, FileOperationResult, IFileStreamContent, IFileService } from 'vs/platform/files/common/files';
import { IFileStatWithMetadata, FileOperationError, FileOperationResult, IFileService } from 'vs/platform/files/common/files';
import { Schemas } from 'vs/base/common/network';
import { stat, chmod, MAX_FILE_SIZE, MAX_HEAP_SIZE } from 'vs/base/node/pfs';
import { join, dirname } from 'vs/base/common/path';
import { isMacintosh } from 'vs/base/common/platform';
import { IProductService } from 'vs/platform/product/common/productService';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/textResourceConfigurationService';
import { UTF8, UTF8_with_bom, toDecodeStream, toEncodeReadable, IDecodeStreamResult } from 'vs/workbench/services/textfile/common/encoding';
import { bufferToStream, VSBufferReadable, VSBuffer } from 'vs/base/common/buffer';
import { createTextBufferFactoryFromStream } from 'vs/editor/common/model/textModel';
import { UTF8, UTF8_with_bom } from 'vs/workbench/services/textfile/common/encoding';
import { ITextSnapshot } from 'vs/editor/common/model';
import { consumeStream } from 'vs/base/common/stream';
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
@@ -34,6 +31,7 @@ import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/commo
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { ILogService } from 'vs/platform/log/common/log';
import { IUriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentity';
import { IModeService } from 'vs/editor/common/services/modeService';
export class NativeTextFileService extends AbstractTextFileService {
@@ -54,68 +52,26 @@ export class NativeTextFileService extends AbstractTextFileService {
@IPathService pathService: IPathService,
@IWorkingCopyFileService workingCopyFileService: IWorkingCopyFileService,
@ILogService private readonly logService: ILogService,
@IUriIdentityService uriIdentityService: IUriIdentityService
@IUriIdentityService uriIdentityService: IUriIdentityService,
@IModeService modeService: IModeService
) {
super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService);
super(fileService, untitledTextEditorService, lifecycleService, instantiationService, modelService, environmentService, dialogService, fileDialogService, textResourceConfigurationService, filesConfigurationService, textModelService, codeEditorService, pathService, workingCopyFileService, uriIdentityService, modeService);
}
async read(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileContent> {
const [bufferStream, decoder] = await this.doRead(resource, {
...options,
// optimization: since we know that the caller does not
// care about buffering, we indicate this to the reader.
// this reduces all the overhead the buffered reading
// has (open, read, close) if the provider supports
// unbuffered reading.
preferUnbuffered: true
});
return {
...bufferStream,
encoding: decoder.detected.encoding || UTF8,
value: await consumeStream(decoder.stream, strings => strings.join(''))
};
// ensure size & memory limits
options = this.ensureLimits(options);
return super.read(resource, options);
}
async readStream(resource: URI, options?: IReadTextFileOptions): Promise<ITextFileStreamContent> {
const [bufferStream, decoder] = await this.doRead(resource, options);
return {
...bufferStream,
encoding: decoder.detected.encoding || UTF8,
value: await createTextBufferFactoryFromStream(decoder.stream)
};
}
private async doRead(resource: URI, options?: IReadTextFileOptions & { preferUnbuffered?: boolean }): Promise<[IFileStreamContent, IDecodeStreamResult]> {
// ensure limits
// ensure size & memory limits
options = this.ensureLimits(options);
// read stream raw (either buffered or unbuffered)
let bufferStream: IFileStreamContent;
if (options.preferUnbuffered) {
const content = await this.fileService.readFile(resource, options);
bufferStream = {
...content,
value: bufferToStream(content.value)
};
} else {
bufferStream = await this.fileService.readFileStream(resource, options);
}
// read through encoding library
const decoder = await toDecodeStream(bufferStream.value, {
guessEncoding: options?.autoGuessEncoding || this.textResourceConfigurationService.getValue(resource, 'files.autoGuessEncoding'),
overwriteEncoding: detectedEncoding => this.encoding.getReadEncoding(resource, options, detectedEncoding)
});
// validate binary
if (options?.acceptTextOnly && decoder.detected.seemsBinary) {
throw new TextFileOperationError(localize('fileBinaryError', "File seems to be binary and cannot be opened as text"), TextFileOperationResult.FILE_IS_BINARY, options);
}
return [bufferStream, decoder];
return super.readStream(resource, options);
}
private ensureLimits(options?: IReadTextFileOptions): IReadTextFileOptions {
@@ -139,28 +95,17 @@ export class NativeTextFileService extends AbstractTextFileService {
}
if (typeof ensuredLimits.memory !== 'number') {
ensuredLimits.memory = Math.max(typeof this.environmentService.args['max-memory'] === 'string' ? parseInt(this.environmentService.args['max-memory']) * 1024 * 1024 || 0 : 0, MAX_HEAP_SIZE);
const maxMemory = this.environmentService.args['max-memory'];
ensuredLimits.memory = Math.max(
typeof maxMemory === 'string'
? parseInt(maxMemory) * 1024 * 1024 || 0
: 0, MAX_HEAP_SIZE
);
}
return ensuredOptions;
}
protected async doEncodeText(resource: URI, value?: string | ITextSnapshot): Promise<VSBuffer | VSBufferReadable | undefined> {
// check for encoding
const { encoding, addBOM } = await this.encoding.getWriteEncoding(resource);
// return to parent when encoding is standard
if (encoding === UTF8 && !addBOM) {
return super.doEncodeText(resource, value);
}
// otherwise create with encoding
const encodedReadable = await this.getEncodedReadable(value || '', encoding, addBOM);
return encodedReadable;
}
async write(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
// check for overwriteReadonly property (only supported for local file://)
@@ -181,20 +126,7 @@ export class NativeTextFileService extends AbstractTextFileService {
}
try {
// check for encoding
const { encoding, addBOM } = await this.encoding.getWriteEncoding(resource, options);
// return to parent when encoding is standard
if (encoding === UTF8 && !addBOM) {
return await super.write(resource, value, options);
}
// otherwise save with encoding
else {
const encodedReadable = await this.getEncodedReadable(value, encoding, addBOM);
return await this.fileService.writeFile(resource, encodedReadable, options);
}
return super.write(resource, value, options);
} catch (error) {
// In case of permission denied, we need to check for readonly
@@ -218,11 +150,6 @@ export class NativeTextFileService extends AbstractTextFileService {
}
}
private getEncodedReadable(value: string | ITextSnapshot, encoding: string, addBOM: boolean): Promise<VSBufferReadable> {
const snapshot = typeof value === 'string' ? stringToSnapshot(value) : value;
return toEncodeReadable(snapshot, encoding, { addBOM });
}
private async writeElevated(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata> {
// write into a tmp file first

View File

@@ -0,0 +1,115 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { workbenchInstantiationService, TestInMemoryFileSystemProvider, TestBrowserTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/browser/workbenchTestServices';
import { NullLogService } from 'vs/platform/log/common/log';
import { FileService } from 'vs/platform/files/common/fileService';
import { Schemas } from 'vs/base/common/network';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { IFileService, IStat } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { join } from 'vs/base/common/path';
import { UTF16le, detectEncodingByBOMFromBuffer, UTF8_with_bom, UTF16be, toCanonicalName } from 'vs/workbench/services/textfile/common/encoding';
import { VSBuffer } from 'vs/base/common/buffer';
import files from 'vs/workbench/services/textfile/test/browser/fixtures/files';
import createSuite from 'vs/workbench/services/textfile/test/common/textFileService.io.test';
import { isWeb } from 'vs/base/common/platform';
import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
// optimization: we don't need to run this suite in native environment,
// because we have nativeTextFileService.io.test.ts for it,
// so our tests run faster
if (isWeb) {
suite('Files - BrowserTextFileService i/o', function () {
const disposables = new DisposableStore();
let service: ITextFileService;
let fileProvider: TestInMemoryFileSystemProvider;
const testDir = 'test';
createSuite({
setup: async () => {
const instantiationService = workbenchInstantiationService();
const logService = new NullLogService();
const fileService = new FileService(logService);
fileProvider = new TestInMemoryFileSystemProvider();
disposables.add(fileService.registerProvider(Schemas.file, fileProvider));
disposables.add(fileProvider);
const collection = new ServiceCollection();
collection.set(IFileService, fileService);
collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new TestWorkingCopyService(), instantiationService, new UriIdentityService(fileService)));
service = instantiationService.createChild(collection).createInstance(TestBrowserTextFileServiceWithEncodingOverrides);
await fileProvider.mkdir(URI.file(testDir));
for (let fileName in files) {
await fileProvider.writeFile(
URI.file(join(testDir, fileName)),
files[fileName],
{ create: true, overwrite: false }
);
}
return { service, testDir };
},
teardown: async () => {
(<TextFileEditorModelManager>service.files).dispose();
disposables.clear();
},
exists,
stat,
readFile,
detectEncodingByBOM
});
async function exists(fsPath: string): Promise<boolean> {
try {
await fileProvider.readFile(URI.file(fsPath));
return true;
}
catch (e) {
return false;
}
}
async function readFile(fsPath: string): Promise<VSBuffer>;
async function readFile(fsPath: string, encoding: string): Promise<string>;
async function readFile(fsPath: string, encoding?: string): Promise<VSBuffer | string> {
const file = await fileProvider.readFile(URI.file(fsPath));
if (!encoding) {
return VSBuffer.wrap(file);
}
return new TextDecoder(toCanonicalName(encoding)).decode(file);
}
async function stat(fsPath: string): Promise<IStat> {
return fileProvider.stat(URI.file(fsPath));
}
async function detectEncodingByBOM(fsPath: string): Promise<typeof UTF16be | typeof UTF16le | typeof UTF8_with_bom | null> {
try {
const buffer = await readFile(fsPath);
return detectEncodingByBOMFromBuffer(buffer.slice(0, 3), 3);
} catch (error) {
return null; // ignore errors (like file not found)
}
}
});
}

File diff suppressed because one or more lines are too long

View File

@@ -9,6 +9,7 @@ import { toResource } from 'vs/base/test/common/utils';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textFileEditorModel';
import { FileOperation } from 'vs/platform/files/common/files';
import { ModesRegistry } from 'vs/editor/common/modes/modesRegistry';
suite('Files - TextFileService', () => {
@@ -132,4 +133,36 @@ suite('Files - TextFileService', () => {
disposable1.dispose();
disposable2.dispose();
});
test('Filename Suggestion - Suggest prefix only when there are no relevant extensions', () => {
ModesRegistry.registerLanguage({
id: 'plumbus0',
extensions: ['.one', '.two']
});
let suggested = accessor.textFileService.suggestFilename('shleem', 'Untitled-1');
assert.equal(suggested, 'Untitled-1');
});
test('Filename Suggestion - Suggest prefix with first extension', () => {
ModesRegistry.registerLanguage({
id: 'plumbus1',
extensions: ['.shleem', '.gazorpazorp'],
filenames: ['plumbus']
});
let suggested = accessor.textFileService.suggestFilename('plumbus1', 'Untitled-1');
assert.equal(suggested, 'Untitled-1.shleem');
});
test('Filename Suggestion - Suggest filename if there are no extensions', () => {
ModesRegistry.registerLanguage({
id: 'plumbus2',
filenames: ['plumbus', 'shleem', 'gazorpazorp']
});
let suggested = accessor.textFileService.suggestFilename('plumbus2', 'Untitled-1');
assert.equal(suggested, 'plumbus');
});
});

View File

@@ -4,79 +4,50 @@
*--------------------------------------------------------------------------------------------*/
import * as assert from 'assert';
import { ITextFileService, snapshotToString, TextFileOperationError, TextFileOperationResult, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
import { URI } from 'vs/base/common/uri';
import { ITextFileService, snapshotToString, TextFileOperationResult, TextFileOperationError, stringToSnapshot } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { Schemas } from 'vs/base/common/network';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { rimraf, RimRafMode, copy, readFile, exists } from 'vs/base/node/pfs';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { tmpdir } from 'os';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { generateUuid } from 'vs/base/common/uuid';
import { join, basename } from 'vs/base/common/path';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { UTF16be, UTF16le, UTF8_with_bom, UTF8 } from 'vs/workbench/services/textfile/common/encoding';
import { DefaultEndOfLine, ITextSnapshot } from 'vs/editor/common/model';
import { UTF16le, UTF8_with_bom, UTF16be, UTF8, UTF16le_BOM, UTF16be_BOM, UTF8_BOM } from 'vs/workbench/services/textfile/common/encoding';
import { VSBuffer } from 'vs/base/common/buffer';
import { createTextModel } from 'vs/editor/test/common/editorTestUtils';
import { ITextSnapshot, DefaultEndOfLine } from 'vs/editor/common/model';
import { isWindows } from 'vs/base/common/platform';
import { readFileSync, statSync } from 'fs';
import { detectEncodingByBOM } from 'vs/workbench/services/textfile/test/node/encoding/encoding.test';
import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import { WorkingCopyFileService, IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
suite('Files - TextFileService i/o', function () {
const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice');
export interface Params {
setup(): Promise<{
service: ITextFileService,
testDir: string
}>
teardown(): Promise<void>
const disposables = new DisposableStore();
exists(fsPath: string): Promise<boolean>;
stat(fsPath: string): Promise<{ size: number }>;
readFile(fsPath: string): Promise<VSBuffer | Buffer>;
readFile(fsPath: string, encoding: string): Promise<string>;
readFile(fsPath: string, encoding?: string): Promise<VSBuffer | Buffer | string>;
detectEncodingByBOM(fsPath: string): Promise<typeof UTF16be | typeof UTF16le | typeof UTF8_with_bom | null>;
}
/**
* Allows us to reuse test suite across different environments.
*
* It introduces a bit of complexity with setup and teardown, however
* it helps us to ensure that tests are added for all environments at once,
* hence helps us catch bugs better.
*/
export default function createSuite(params: Params) {
let service: ITextFileService;
let testDir: string;
// Given issues such as https://github.com/microsoft/vscode/issues/78602
// and https://github.com/microsoft/vscode/issues/92334 we see random test
// failures when accessing the native file system. To diagnose further, we
// retry node.js file access tests up to 3 times to rule out any random disk
// issue and increase the timeout.
this.retries(3);
this.timeout(1000 * 10);
let testDir = '';
const { exists, stat, readFile, detectEncodingByBOM } = params;
setup(async () => {
const instantiationService = workbenchInstantiationService();
const logService = new NullLogService();
const fileService = new FileService(logService);
const fileProvider = new DiskFileSystemProvider(logService);
disposables.add(fileService.registerProvider(Schemas.file, fileProvider));
disposables.add(fileProvider);
const collection = new ServiceCollection();
collection.set(IFileService, fileService);
collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new TestWorkingCopyService(), instantiationService, new UriIdentityService(fileService)));
service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides);
const id = generateUuid();
testDir = join(parentDir, id);
const sourceDir = getPathFromAmdModule(require, './fixtures');
await copy(sourceDir, testDir);
const result = await params.setup();
service = result.service;
testDir = result.testDir;
});
teardown(async () => {
(<TextFileEditorModelManager>service.files).dispose();
disposables.clear();
await rimraf(parentDir, RimRafMode.MOVE);
await params.teardown();
});
test('create - no encoding - content empty', async () => {
@@ -84,7 +55,8 @@ suite('Files - TextFileService i/o', function () {
await service.create(resource);
assert.equal(await exists(resource.fsPath), true);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, 0 /* no BOM */);
});
test('create - no encoding - content provided (string)', async () => {
@@ -92,8 +64,9 @@ suite('Files - TextFileService i/o', function () {
await service.create(resource, 'Hello World');
assert.equal(await exists(resource.fsPath), true);
assert.equal((await readFile(resource.fsPath)).toString(), 'Hello World');
const res = await readFile(resource.fsPath);
assert.equal(res.toString(), 'Hello World');
assert.equal(res.byteLength, 'Hello World'.length);
});
test('create - no encoding - content provided (snapshot)', async () => {
@@ -101,8 +74,9 @@ suite('Files - TextFileService i/o', function () {
await service.create(resource, stringToSnapshot('Hello World'));
assert.equal(await exists(resource.fsPath), true);
assert.equal((await readFile(resource.fsPath)).toString(), 'Hello World');
const res = await readFile(resource.fsPath);
assert.equal(res.toString(), 'Hello World');
assert.equal(res.byteLength, 'Hello World'.length);
});
test('create - UTF 16 LE - no content', async () => {
@@ -114,6 +88,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF16le);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, UTF16le_BOM.length);
});
test('create - UTF 16 LE - content provided', async () => {
@@ -125,6 +102,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF16le);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, 'Hello World'.length * 2 /* UTF16 2bytes per char */ + UTF16le_BOM.length);
});
test('create - UTF 16 BE - no content', async () => {
@@ -136,6 +116,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF16be);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, UTF16le_BOM.length);
});
test('create - UTF 16 BE - content provided', async () => {
@@ -147,6 +130,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF16be);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, 'Hello World'.length * 2 /* UTF16 2bytes per char */ + UTF16be_BOM.length);
});
test('create - UTF 8 BOM - no content', async () => {
@@ -158,6 +144,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF8_with_bom);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, UTF8_BOM.length);
});
test('create - UTF 8 BOM - content provided', async () => {
@@ -169,6 +158,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF8_with_bom);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, 'Hello World'.length + UTF8_BOM.length);
});
test('create - UTF 8 BOM - empty content - snapshot', async () => {
@@ -180,6 +172,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF8_with_bom);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, UTF8_BOM.length);
});
test('create - UTF 8 BOM - content provided - snapshot', async () => {
@@ -191,6 +186,9 @@ suite('Files - TextFileService i/o', function () {
const detectedEncoding = await detectEncodingByBOM(resource.fsPath);
assert.equal(detectedEncoding, UTF8_with_bom);
const res = await readFile(resource.fsPath);
assert.equal(res.byteLength, 'Hello World'.length + UTF8_BOM.length);
});
test('write - use encoding (UTF 16 BE) - small content as string', async () => {
@@ -229,7 +227,7 @@ suite('Files - TextFileService i/o', function () {
});
test('write - use encoding (shiftjis)', async () => {
await testEncodingKeepsData(URI.file(join(testDir, 'some_shiftjs.txt')), 'shiftjis', '中文abc');
await testEncodingKeepsData(URI.file(join(testDir, 'some_shiftjis.txt')), 'shiftjis', '中文abc');
});
test('write - use encoding (gbk)', async () => {
@@ -401,8 +399,12 @@ suite('Files - TextFileService i/o', function () {
const result = await service.readStream(resource);
assert.equal(result.name, basename(resource.fsPath));
assert.equal(result.size, statSync(resource.fsPath).size);
assert.equal(snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)), snapshotToString(createTextModel(readFileSync(resource.fsPath).toString()).createSnapshot(false)));
assert.equal(result.size, (await stat(resource.fsPath)).size);
const content = (await readFile(resource.fsPath)).toString();
assert.equal(
snapshotToString(result.value.create(DefaultEndOfLine.LF).createSnapshot(false)),
snapshotToString(createTextModel(content).createSnapshot(false)));
}
test('read - small text', async () => {
@@ -421,8 +423,8 @@ suite('Files - TextFileService i/o', function () {
const result = await service.read(resource);
assert.equal(result.name, basename(resource.fsPath));
assert.equal(result.size, statSync(resource.fsPath).size);
assert.equal(result.value, readFileSync(resource.fsPath).toString());
assert.equal(result.size, (await stat(resource.fsPath)).size);
assert.equal(result.value, (await readFile(resource.fsPath)).toString());
}
test('readStream - encoding picked up (CP1252)', async () => {
@@ -506,7 +508,7 @@ suite('Files - TextFileService i/o', function () {
await testLargeEncoding('gbk', '中国abc');
});
test('readStream - large ShiftJS', async () => {
test('readStream - large ShiftJIS', async () => {
await testLargeEncoding('shiftjis', '中文abc');
});
@@ -588,4 +590,4 @@ suite('Files - TextFileService i/o', function () {
const result = await service.read(URI.file(join(testDir, 'small.txt')), { acceptTextOnly: true });
assert.equal(result.name, 'small.txt');
});
});
}

View File

@@ -0,0 +1,84 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IFileService } from 'vs/platform/files/common/files';
import { TextFileEditorModelManager } from 'vs/workbench/services/textfile/common/textFileEditorModelManager';
import { Schemas } from 'vs/base/common/network';
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
import { rimraf, RimRafMode, copy, readFile, exists, stat } from 'vs/base/node/pfs';
import { DisposableStore } from 'vs/base/common/lifecycle';
import { FileService } from 'vs/platform/files/common/fileService';
import { NullLogService } from 'vs/platform/log/common/log';
import { getRandomTestPath } from 'vs/base/test/node/testUtils';
import { tmpdir } from 'os';
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
import { generateUuid } from 'vs/base/common/uuid';
import { join } from 'vs/base/common/path';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { detectEncodingByBOM } from 'vs/workbench/services/textfile/test/node/encoding/encoding.test';
import { workbenchInstantiationService, TestNativeTextFileServiceWithEncodingOverrides } from 'vs/workbench/test/electron-browser/workbenchTestServices';
import createSuite from 'vs/workbench/services/textfile/test/common/textFileService.io.test';
import { IWorkingCopyFileService, WorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { TestWorkingCopyService } from 'vs/workbench/test/common/workbenchTestServices';
import { UriIdentityService } from 'vs/workbench/services/uriIdentity/common/uriIdentityService';
suite('Files - NativeTextFileService i/o', function () {
const parentDir = getRandomTestPath(tmpdir(), 'vsctests', 'textfileservice');
const disposables = new DisposableStore();
let service: ITextFileService;
let testDir: string;
// Given issues such as https://github.com/microsoft/vscode/issues/78602
// and https://github.com/microsoft/vscode/issues/92334 we see random test
// failures when accessing the native file system. To diagnose further, we
// retry node.js file access tests up to 3 times to rule out any random disk
// issue and increase the timeout.
this.retries(3);
this.timeout(1000 * 10);
createSuite({
setup: async () => {
const instantiationService = workbenchInstantiationService();
const logService = new NullLogService();
const fileService = new FileService(logService);
const fileProvider = new DiskFileSystemProvider(logService);
disposables.add(fileService.registerProvider(Schemas.file, fileProvider));
disposables.add(fileProvider);
const collection = new ServiceCollection();
collection.set(IFileService, fileService);
collection.set(IWorkingCopyFileService, new WorkingCopyFileService(fileService, new TestWorkingCopyService(), instantiationService, new UriIdentityService(fileService)));
service = instantiationService.createChild(collection).createInstance(TestNativeTextFileServiceWithEncodingOverrides);
const id = generateUuid();
testDir = join(parentDir, id);
const sourceDir = getPathFromAmdModule(require, './fixtures');
await copy(sourceDir, testDir);
return { service, testDir };
},
teardown: async () => {
(<TextFileEditorModelManager>service.files).dispose();
disposables.clear();
await rimraf(parentDir, RimRafMode.MOVE);
},
exists,
stat,
readFile,
detectEncodingByBOM
});
});

View File

@@ -11,6 +11,8 @@ import * as streams from 'vs/base/common/stream';
import * as iconv from 'iconv-lite-umd';
import { getPathFromAmdModule } from 'vs/base/common/amd';
import { newWriteableBufferStream, VSBuffer, VSBufferReadableStream, streamToBufferReadableStream } from 'vs/base/common/buffer';
import { SUPPORTED_ENCODINGS } from 'vs/workbench/services/textfile/common/textfiles';
import { isWindows } from 'vs/base/common/platform';
export async function detectEncodingByBOM(file: string): Promise<typeof encoding.UTF16be | typeof encoding.UTF16le | typeof encoding.UTF8_with_bom | null> {
try {
@@ -296,7 +298,6 @@ suite('Encoding', () => {
assert.equal(content, '');
});
test('toDecodeStream - encoding, utf16be', async function () {
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
const source = streamToBufferReadableStream(fs.createReadStream(path));
@@ -311,7 +312,6 @@ suite('Encoding', () => {
assert.equal(actual, expected);
});
test('toDecodeStream - empty file', async function () {
const path = getPathFromAmdModule(require, './fixtures/empty.txt');
const source = streamToBufferReadableStream(fs.createReadStream(path));
@@ -334,12 +334,38 @@ suite('Encoding', () => {
const source = newTestReadableStream(buffers);
const { stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async detected => detected || encoding.UTF8 });
const expected = incompleteEmojis.toString(encoding.UTF8);
const expected = new TextDecoder().decode(incompleteEmojis);
const actual = await readAllAsString(stream);
assert.equal(actual, expected);
});
test('toDecodeStream - some stream (GBK issue #101856)', async function () {
const path = getPathFromAmdModule(require, './fixtures/some_gbk.txt');
const source = streamToBufferReadableStream(fs.createReadStream(path));
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async () => 'gbk' });
assert.ok(detected);
assert.ok(stream);
const content = await readAllAsString(stream);
assert.equal(content.length, 65537);
});
(isWindows /* unsupported OS */ ? test.skip : test)('toDecodeStream - some stream (UTF-8 issue #102202)', async function () {
const path = getPathFromAmdModule(require, './fixtures/issue_102202.txt');
const source = streamToBufferReadableStream(fs.createReadStream(path));
const { detected, stream } = await encoding.toDecodeStream(source, { minBytesRequiredForDetection: 4, guessEncoding: false, overwriteEncoding: async () => 'utf-8' });
assert.ok(detected);
assert.ok(stream);
const content = await readAllAsString(stream);
const lines = content.split('\n');
assert.equal(lines[981].toString(), '啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。');
});
test('toEncodeReadable - encoding, utf16be', async function () {
const path = getPathFromAmdModule(require, './fixtures/some_utf16be.css');
const source = await readAndDecodeFromDisk(path, encoding.UTF16be);
@@ -399,4 +425,14 @@ suite('Encoding', () => {
assert.equal(actual, expected);
});
});
test('encodingExists', async function () {
for (const enc in SUPPORTED_ENCODINGS) {
if (enc === encoding.UTF8_with_bom) {
continue; // skip over encodings from us
}
assert.equal(iconv.encodingExists(enc), true, enc);
}
});
});

View File

@@ -0,0 +1,983 @@
%啊啊
%
% 啊啊啊aaaaa
% 啊啊啊aaa啊啊、啊啊啊啊啊
% 啊啊啊啊啊啊啊啊啊
%
% 啊啊啊啊啊啊啊啊啊aa啊啊啊啊a2aa啊啊啊啊啊aaaaa
% 啊啊啊啊啊啊
%
% 啊啊啊啊,啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊。
% 啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊。
\aaaaaaa{啊啊啊啊啊啊啊}%
\aaaaa{aa:aa}
%\aaaaaaa
% 啊啊
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊、啊啊、啊啊、啊啊、啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊,
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaa aaaa aaaaa, aaa啊啊啊啊啊aaaaa-aaaaa aaaaa, aaa啊啊啊啊啊啊啊啊啊啊
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaaaaa aaaaa aaaaa aaaaaaaa啊啊啊啊啊啊啊aaaaaaaaa aaaaa aaaaaaaaaa啊啊啊啊啊啊。
啊啊,啊啊啊啊、啊啊、啊啊,啊啊啊啊啊啊啊啊啊。
% 啊啊啊啊
%% (啊啊啊啊啊啊啊啊)
%% 啊啊
\aaaaaa{啊啊}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%%% 啊啊
啊啊啊啊啊啊啊啊啊啊\aaaaaa{啊啊啊啊啊啊啊啊}。
啊啊啊啊啊啊\aaaaaa{啊啊啊啊}aaaa aaaa啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaa{啊啊啊啊啊}aaaa aaaaaaaa
%%% 啊啊
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊(\aaaaaaaa{aaaa})、啊啊(\aaaaaaaa{aaaaa})、啊啊(\aaaaaaaa{aaaa})、啊啊(\aaaaaaaa{aaaaa})啊啊啊(\aaaaaaaa{aaaaa})啊。
%% 啊啊(啊啊啊啊啊啊)
\aaaaaa{啊啊啊}啊啊啊啊啊啊啊啊啊,啊啊啊啊啊\aaaaaa{啊啊}啊。
啊啊啊啊啊啊“啊啊”啊啊,啊啊啊啊啊啊(啊啊啊啊啊啊啊啊啊啊啊)啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊啊啊啊啊)。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaa{啊啊啊}。
啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊啊(\aaaaaaaa{aaaaa})啊啊啊啊啊(\aaaaaaaa{aaaaa})啊)。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊\aaaaaa{啊啊啊啊}。
%%
\aaaaaa{啊啊啊啊}啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊:啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊。}
%\aaaaa{aaaaaaaaa}
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊“啊啊啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊“啊啊啊啊啊啊aaa4啊啊啊啊啊啊啊啊啊啊啊”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{aaaaaaaaa}
% 啊啊啊啊
啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaa{啊啊啊}aaaaa aaaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaa{啊}aaaaa
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊512啊啊啊4aa。
啊啊啊啊啊啊啊啊啊啊啊,啊啊\aaaaaa{啊啊}。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊
%啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊、啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊
%啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊。
% aaaa (aa): 啊啊啊啊啊啊啊aaaa
% aaaa (aa): 啊啊啊啊啊啊啊aa啊啊啊啊aaaaaa啊啊啊aaaaaaaa啊啊
%啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,
啊~\aaa{aaa:aa:aaaaa}啊啊啊aaaaa啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊、啊啊啊啊a/a啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊\aaaaaaaa{aaaa}啊\aaaaaaaa{aaaaa}啊)。
啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊\aaaaaa{啊啊啊啊啊啊}aaaaaaa aaaa aaaaaaaaa啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊,啊\aaaaaa{啊啊啊}aaaa aaaaa、\aaaaaa{aaaaa啊啊}aaaaaa啊\aaaaaa{啊啊啊啊啊}aaaaaa啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaaa啊aaaaaa啊啊啊啊啊啊aaaaa啊aaaaaa啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊。
%啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊:啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊;
%啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊:啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊。
aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊a/a啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊a/a啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaa}[aa]
\aaaaaaaaa
\aaaaaaaaaaaaaaa[aaaaa=0.5\aaaaaaaaa]{aaaa/aaaa/aa/aaaaa.aaa} \\[1aa]
\aaaaaaa{啊啊啊}%
\aaaaa{aaa:aa:aaaaa}
\aaa{aaaaaa}
啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊{\aaaaaa}啊啊啊啊啊啊啊啊。
\aaaaaaa
啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊:
\aaaaa{aaaaaaaaa}
\aaaa 啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊?(啊\aaa{aaa:aa:aaaaaaa}啊)
\aaaa 啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊?(啊\aaa{aaa:aa:aaaaaaa}啊)
\aaaa 啊啊啊啊啊啊啊啊啊啊啊?(啊\aaa{aaa:aa:aaaaaaa}啊)
% \aaaa 啊啊啊啊啊啊啊,啊啊啊啊啊啊啊?
% \aaaa 啊啊啊啊啊啊啊10啊啊啊啊啊啊啊啊啊100a啊啊啊啊啊啊啊啊啊100aa啊啊啊啊啊啊啊
\aaaa 啊啊啊啊a啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaa{aaa:aa:aaa}啊)
% \aaaa 啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊
% \aaaa 啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊?
\aaaa 啊啊啊啊aaaa啊“啊啊啊啊啊”啊啊啊、啊啊啊啊啊啊啊啊\aaa{aaa:aa:aaa}啊)
\aaaa 啊啊啊啊啊啊啊啊啊啊啊?\aaaaaaaa{/aaaa}啊\aaaaaaaa{/aaa}啊啊啊啊?(啊\aaa{aaa:aa:aaa}啊)
% \aaaa 啊啊啊aaaaaaa啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊\aaa{aaa:aa:aaaaaa}啊)
% \aaaa 啊啊啊啊啊啊啊aaaa啊aaaa啊啊啊啊
\aaaa aaaaaaa啊啊啊啊啊啊啊啊啊啊aaaa啊啊啊啊啊啊啊啊啊啊\aaa{aaa:aa:aaaaaa}啊)
\aaaa 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?(啊\aaa{aaa:aa:aaaaaa}啊)
\aaa{aaaaaaaaa}
\aa
\aaaaaaa{啊啊aaaaa啊啊啊啊啊}%
\aaaaa{aaa:aa:aaaaaaa}
\aaaaa{aaaaaaaaa}
\aaaa 啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
\aaaa 啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
\aaa{aaaaaaaaa}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊啊啊啊啊啊啊啊啊啊)。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaa}[a]
\aaaaaaaaa
\aaaaaaa{aaaaa啊啊啊啊啊啊啊啊啊啊啊}%
\aaaaa{aaa:aa:aaaaaa-aaaaa-aaaaaaaa}
\aaaaa{aaaaaaa}{aa}
\aaaaaaa
\aaaaaa{啊啊啊啊啊} & \aaaaaa{啊啊} \\
\aaaaaaa
aaaa & 啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊 \\
aaaaa & 啊啊啊啊啊啊啊啊啊啊 \\
aaa & 啊啊啊啊啊啊啊aa \\
aaa & 啊啊啊啊啊啊啊啊aa \\
aaaa & 啊啊啊啊啊 \\
aaaaa & 啊啊啊啊啊啊啊啊啊啊 \\
aaaaa & 啊啊啊啊啊啊啊啊啊啊 \\
aaaaa & 啊啊啊啊啊啊啊啊啊啊 \\
\aaaaaaaaaa
\aaa{aaaaaaa}
\aaa{aaaaa}
\aaaaaaaaaa{aaaaa}
%aaaaa啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊aaaaa啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊。
%啊啊啊啊,啊啊啊啊啊啊啊\aaaaaa{aaaaa},啊啊啊啊啊啊啊啊啊。
aaaaa啊啊啊啊啊aaaaa aaaa啊啊啊啊啊啊啊啊啊啊啊。
aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊~\aaa{aaa:aa:aaaaaa-aaaaa-aaaaaaaa}啊啊啊啊啊啊啊啊、啊啊啊啊啊、啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
%啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊~\aaa{aaa:aa:aaaa-aaaaa}),啊啊\aaaaaa{啊啊啊啊}aaaaaaa aaaa、\aaaaaa{啊啊啊啊}aaaaaaaaa aaaa啊\aaaaaa{啊啊啊啊啊啊}aaaaaaaa aaaa aaaa
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaa}[a]
\aaaaaaaaa
\aaaaaaa{aaaaa啊啊啊啊啊啊啊啊}%
\aaaaa{aaa:aa:aaaa-aaaaa}
\aaaaa{aaaaaaa}{aa}
\aaaaaaa
\aaaaaa{啊啊啊啊} & \aaaaaa{啊啊啊啊} \\
\aaaaaaa
啊啊啊啊 & 啊啊啊啊 \\
啊啊啊啊 & 啊啊啊啊啊啊啊啊啊 \\
啊啊啊啊啊啊 & 啊啊啊啊啊啊(啊啊啊啊啊啊啊啊啊) \\
aaaa啊啊 & 啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊 \\
啊啊啊啊啊 & 啊啊啊啊啊啊啊aaaa啊啊啊啊啊啊 \\
啊啊啊啊啊啊 & 啊啊啊啊啊啊啊啊啊 \\
啊啊啊啊啊 & 啊啊啊啊啊啊啊啊 \\
\aaaaaaaaaa
\aaa{aaaaaaa}
\aaa{aaaaa}
\aaaaaaaaaa{啊啊啊啊}
% % aaaaaaa(aa): 啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?
%啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊、啊啊、啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊。
啊啊、啊啊、啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊,
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊,
啊~\aaa{aaa:aa:aaaaaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。}。
啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊4aa啊啊啊啊啊啊8啊啊啊啊啊aaaaa啊啊啊啊啊啊12啊啊啊啊啊啊3啊啊啊啊啊啊啊啊1啊。
\aaaaa{aaaaaa}[aaa]
\aaaaaaaaa
\aaaaaaaaaaaaaaa[aaaaa=0.9\aaaaaaaaa]{aaaa/aaaa/aa/aaaaa.aaa} \\[1aa]
\aaaaaaa{啊啊啊啊啊啊啊啊}%
\aaaaa{aaa:aa:aaaaaaaa}
\aaa{aaaaaa}
啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊4aa啊啊啊啊啊啊啊啊0啊4095啊啊啊啊0啊4095
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊4096啊8191啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊4096\(\aaaaa\)11啊4096\(\aaaaa\)12\(-\)1啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊48aa啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊48aa啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊4aa啊啊啊啊啊啊啊啊啊啊512啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊512啊4aa啊啊啊啊啊啊2aa啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊2aa啊啊啊啊啊4aa啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊4096\(\aaaaa\)12啊4096\(\aaaaa\)13\(-\)1啊4096啊啊啊。
啊啊啊啊啊啊啊啊啊aaaaa啊啊3啊啊啊啊啊啊啊啊啊6aa啊啊啊。
啊啊啊啊啊啊啊啊啊啊48aa\(+\)6aa啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊512啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊512啊啊啊啊啊啊啊啊啊啊啊2aa\(\aaaaa\)512啊1aa啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊48aa\(+\)6aa\(+\)1aa。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{啊啊啊啊啊aaaaa啊“啊啊啊啊”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。}。
啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊a+啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaaa{啊啊啊啊}
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊啊~\aaa{aaaaa:aa:aaa-aaa-aaaa})。
啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊——啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaaaaa}[aaaaaaa=啊啊\aaaaaaaaaaa{aaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊, aaaaa=aaaaa:aa:aaa-aaa-aaaa]
[aaaa@aaaaaa ~] # aaaa
.
└── aa-aaaaaaaaa
├── aaaaa-aaa
│   └── aaaaa-aaaa.aa3
├── aaaaaaa.aaa
├── aaaaaaaa-aaa
│   ├── aaa-aaaa.aaa
│   └── aaaa-aaaa.aaa
├── aaaaa-aaa
└── aaaaa-aaa
└── aaaaa-aaaa.aaa
5 aaaaaaaaaaa, 5 aaaaa
\aaa{aaaaaaaaa}
\aaaaaaa
啊啊:啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊,“啊啊”啊“啊啊啊”啊啊啊啊啊,啊啊啊啊啊啊啊:啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊;啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa aaaaaa啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa aaaaaa啊啊啊。
啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaa aaaa。啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊。
\aa
啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊~\aaa{aaa:aa:aaaaaaaa}啊啊啊啊啊啊啊啊。
啊~\aaa{aaa:aa:aaaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊、啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa。}。
\aaaaa{aaaaaa}[aaa]
\aaaaaaaaa
\aaaaaaaaaaaaaaa[aaaaa=0.9\aaaaaaaaa]{aaaa/aaaa/aa/aaaaaa.aaa} \\[1aa]
\aaaaaaa{啊啊啊啊啊啊啊啊:啊啊啊}%
\aaaaa{aaa:aa:aaaaaa}
\aaa{aaaaaa}
啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊、啊啊、啊啊啊啊啊啊啊啊:
\aaaaa{aaaaaaa}
\aaaaaaaaa\aaaaaaa{-0.5aa}
\aaaa 啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊aaaaa啊啊啊aaaaa啊啊啊啊啊啊啊。
\aaaa 啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊啊啊啊)啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaa 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊。
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊(啊啊啊啊啊啊)啊啊啊,啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaa 啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊0啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaa{aaaaaaa}
\aaaaa{aaaaaaa-aaaa}[啊啊啊]
啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊“.”啊“..”啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊。
啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
啊啊啊啊啊啊啊,“.”啊“..”啊啊啊aaaaa啊啊啊啊啊啊啊啊。
\aaa{aaaaaaa-aaaa}
\aaaaaaaaaa{啊啊啊啊啊啊啊啊}
\aaaaaaaaaaaaa{啊啊啊啊}
\aaaaaa{啊啊啊啊}啊啊啊\aaaaaa{啊啊啊},啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaa{啊啊啊啊}。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊,啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊aaaaa啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊,
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊:啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaaaaaa{啊啊啊}
% 啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊\aaaaaa{啊啊啊}。
啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa
啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊。
% 啊啊啊啊啊啊啊啊啊啊啊啊啊啊“啊啊啊”啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊。
\aaaaaaaaaaaaa{啊啊啊啊啊啊啊啊啊}
啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊~\aaa{aaa:aa:aaaaaa-aaaaa-aaaaaaaa}啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊1啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊1。
啊aaaaa啊啊啊啊啊0啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊2啊啊啊啊啊啊啊啊啊啊啊啊啊1。
%啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊“.”)。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊“..”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa。
%啊啊啊啊啊aaaaa啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊。
%啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊aaaaa啊啊啊“.”啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊“..”啊啊啊啊。
%啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊“.”啊“..”啊啊啊啊啊啊。
%啊啊啊,啊啊啊啊啊啊,啊啊啊啊。
%啊啊啊啊,啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊aaaaa啊啊“..”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊1。
\aaaaa{aaaaaaa-aaaa}[啊啊啊]
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊“.”啊“..”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊2啊啊啊啊啊啊啊啊啊啊啊啊1。
啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊“.”啊“..”啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊:
啊啊啊啊啊,啊啊啊啊啊啊\aaaaaaaa{aa -a aaa}啊啊啊\aaaaaaaa{aaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?
啊啊啊,啊\aaaaaaaa{aa}啊啊啊啊\aaaaaaaa{-a}啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊,啊“啊啊啊啊”啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{aaa}啊啊啊啊。
\aaa{aaaaaaa-aaaa}
\aaaaaaaaaaaaa{啊啊啊啊啊啊啊啊啊啊啊}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊),啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa。
啊啊,
%啊啊,啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊“啊啊啊啊啊啊啊”啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{啊啊啊啊,“.”啊“..”啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊。}。
啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊;
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。}
% 啊啊啊啊啊啊啊
% aaaa (aa): aaaaa aaaaaaaaa aaaaaaa aaa aaaa aaaa
\aaaaaaaaaa{啊啊啊啊}
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊。
% 啊啊aaa4啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa aaaaa啊啊啊啊啊啊啊啊啊啊啊啊
%啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊。
% aaaa(aa): 啊啊啊啊啊啊aaa2、aaaaa啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊。
%,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊~\aaa{aaa:aa:aa-aaaa-aaaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊、aaaaa啊啊啊啊、aaaaa啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊)啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊~\aaa{aaa:aa:aa-aaaa-aaaaaa}啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaa}[aa]
\aaaaaaaaa
\aaaaaaaaaaaaaaa[aaaaa=0.9\aaaaaaaaa]{aaaa/aaaa/aa/aa-aaaa-aaaaaa.aaa} \\[1aa]
\aaaaaaa{啊啊啊啊啊啊啊啊啊}%
\aaaaa{aaa:aa:aa-aaaa-aaaaaa}
\aaa{aaaaaa}
啊啊啊啊,\aaaaaa{啊啊啊}aaaaa aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊aaaaa aaaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊、啊啊啊啊啊啊啊aaaaa啊啊啊、啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊1啊aaaaa啊啊9.1.1啊啊啊啊啊啊啊啊啊啊啊啊啊啊2啊aaaaa啊啊啊啊啊啊啊9.1.1啊啊,啊啊啊啊啊啊}
\aaaaa{aaaaaaaa}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊1啊aaaaa啊啊啊啊aaaaa啊啊啊啊
啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaa{aaaaaaaa}
啊啊啊啊啊啊,啊啊啊啊啊啊啊啊\aaaaaa{啊啊啊啊啊}啊\aaaaaa{aaaaa啊啊啊啊}。
啊啊啊啊啊啊啊啊啊aaaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊1啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊0啊啊啊啊啊啊啊啊啊啊啊。
aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊aaaaa啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaa{aaaaa啊}。
aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊。
啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊\aaaaaa{aaaaa啊}啊啊啊aaaaa啊啊啊啊。
啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊aaaaa啊啊。
%\aaa{啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊——啊啊啊啊啊aaaaa啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊\aaaaaaaa{啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。}。
啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊}
\aaaaaaa{啊啊啊啊啊啊}\aaaaa{aaa:aa:aaa}
\aaaaa{aaaaaaaaa}
\aaaa 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?
\aaaa 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?
\aaaa 啊啊啊啊啊啊啊啊?啊啊啊啊啊啊啊啊啊啊?
\aaa{aaaaaaaaa}
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊a啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊,啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊、啊啊啊啊啊“啊啊”啊啊啊啊啊啊啊啊,
%啊啊啊啊啊啊啊啊啊啊啊啊啊“啊啊啊”,啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%%啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%%啊啊,啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊“啊啊啊”。
%%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%%啊啊啊啊啊、啊啊啊啊啊aaa啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊,啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊“啊啊”啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaa啊aaa4啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊“啊啊啊”啊啊aaa4啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa32啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaa啊aaa4啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa32啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊,\aaaaaaa{啊啊啊啊啊啊}aaaaaaa aaaa aaaaaaaaa\aaaaaaaa{啊啊aaaaaaa aaaaaaaaaa aaaaaa。}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊~\aaa{aaaaa:aa:aaaaa}啊啊啊啊aaaaa啊啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊\aaaaaaa{啊啊啊啊啊啊}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊、啊啊啊啊啊啊啊啊啊啊啊。
啊\aaaaaaaa{/aaa/aaa1}啊啊啊啊啊啊啊,啊啊啊\aaaaaaaa{/aaa/aaa}啊啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊啊啊啊啊啊啊啊“/”)啊。啊啊啊啊啊\aaaaaaaa{aa}啊啊啊啊啊啊、\aaaaaaaa{aaaaaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊24啊啊啊啊啊啊啊啊啊啊啊啊。}、\aaaaaaaa{aaaaaa=aaaaaaa-aa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaaaaa}[aaaaaaa=啊aaaaa啊啊啊啊啊啊啊\aaaaaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊, aaaaa=aaaaa:aa:aaaaa]
# 啊啊啊啊:啊啊 aa 啊啊啊 aaaa 啊啊啊啊啊啊 (啊啊啊啊)
[aaaa@aaaaaa ~] # aaaaa
aaaaa aa /aaa aaaa aaaaa (aa,aaaaaa,aaaaa,aaaaaa,aaaaaaaa)
aaaa aa /aaaa aaaa aaaa (aa,aaaaaa,aaaaa,aaaaaa,aaaaaaaa)
aaaaa aa /aaa aaaa aaaaa (aa,aaaaaa,aaaaaaaa,aaaa=2050804a,aaaa=755)
/aaa/aaa1 aa / aaaa aaa4 (aa,aaaaaaaa,aaaaaa=aaaaaaa-aa)
aaaaa aa /aaa/aaa aaaa aaaaa (aa,aaaaaa,aaaaa)
aaaaaaaaa aa /aaa/aaaaaaaaa aaaa aaaaaaaaa (aa,aaaaaaaa,aaaaaaaa=2a)
aaaaaaa aa /aaa/aaaaaa/aaaaa aaaa aaaaaaa (aa,aaaaaaaa)
aaaaaa aa /aaa/aaa_aaaaaa aaaa aaa_aaaaaa (aa,aaaaaaaa)
/aaa/aaa3 aa /aaaaa/aaa aaaa aaa4 (aa,aaaaaa,aaaaa,aaaaaaaa,aaaa)
\aaa{aaaaaaaaa}
%aaaa aa /aaa aaaa aaaaaaaa (aa,aaaaaa,aaaaaaaa,aaaa=10237212a,aa_aaaaaa=2559303,aaaa=755)
%aaaaaaaaaa aa /aaa/aaaaaa/aaaaaaaa aaaa aaaaaaaaaa (aa,aaaaaa,aaaaa,aaaaaa,aaaaaaaa)
%aaaaaa aa /aaa/aaa aaaa aaaaaa (aa,aaaaaa,aaaaaa,aaaaaaaa,aaa=5,aaaa=620,aaaaaaaa=000)
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊?
%啊啊啊啊啊啊啊啊啊啊啊啊:
%啊啊啊啊啊啊aaa\aaaaaaaa{啊 aaaaaaa aaaa aaaaaa 啊 aaaaaaa aaaaaaaaaa aaaaaa。啊啊啊啊啊啊啊啊啊啊啊啊啊。}。
%啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊。
% 啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
% 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊、啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊“啊啊”。
啊啊啊啊“啊啊”啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊aaaaa啊啊aaa~\aaaa{aaaaa-aaa-aaa}啊啊,
啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaaa{啊啊啊啊啊啊啊啊啊}
aaa啊啊啊啊啊啊啊啊“啊啊”啊啊啊啊\aaaa{啊啊啊啊}啊\aaaa{啊啊啊啊}啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊
啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?}
\aaaaaaaaaaaaa{啊啊啊啊}
啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊,啊啊啊啊啊啊啊。
%%\aaa{啊啊啊啊啊啊啊?}
啊~\aaa{aaa:aa:aaa-aa-aaaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊aaa4啊啊啊啊啊啊啊\aaaa{啊啊啊啊}啊啊aaa4啊啊啊啊啊“啊啊啊啊”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊“啊啊”,啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊啊啊啊“啊啊啊啊”),
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaa}[aaa]
\aaaaaaaaa
\aaaaaaaaaaaaaaa[aaaaa=0.5\aaaaaaaaa]{aaaa/aaaa/aa/aaa-aa-aaaaaa.aaa} \\[1aa]
\aaaaaaa{啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊}%
\aaaaa{aaa:aa:aaa-aa-aaaaaa}
\aaa{aaaaaa}
%
%\aaaaa{aaaaaaa-aaaa}[啊啊啊啊啊啊“啊啊”]
%
%啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊“啊啊”。
%啊啊,啊啊啊啊啊啊啊啊啊啊“啊啊”。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊“啊啊”啊?
%%啊啊啊啊啊啊啊啊啊啊?
%\aaa{aaaaaaa-aaaa}
\aaaaa{aaaaaaa}[aa]
\aaaaa{aaaaaaaaaaaaa}
aaaaaa aaaa_aaaaaa_aaaa {
aaaaa aaaa *aaaa;
...
aaaaaa aaaaaa *(*aaaaa) (aaaaaa aaaa_aaaaaa_aaaa *,
aaa, aaaaa aaaa *, aaaa *);
aaaa (*aaaa_aa) (aaaaaa aaaaa_aaaaa *);
aaaaaa aaaaaa *aaaaa;
aaaaaa aaaa_aaaaaa_aaaa * aaaa;
aaaaaa aaaaa_aaaa aa_aaaaaa;
...
};
\aaa{aaaaaaaaaaaaa}
\aaaaaaa{aaaaa aaa啊啊啊啊啊啊啊啊啊啊}%
\aaaaa{aaaa:aa:aaa-aaaaaa}
\aaa{aaaaaaa}
%啊aaaaa啊啊啊啊啊啊“啊啊”啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
aaaaa啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa。
%aaaaa啊啊啊啊啊啊啊啊啊“啊啊”啊啊啊啊啊啊啊。
%啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{啊啊啊啊啊啊啊啊啊啊啊?}
%啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊~\aaa{aaa:aa:aaa-aa-aaaaaa}啊啊啊啊啊啊),啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊~\aaa{aaaa:aa:aaa-aaaaaa}啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaaa}[aa]
\aaaaa{aaaaaaaaaaaaa}
aaaaaa aaaaa_aaaaaaaaaa {
// aaaaa啊啊啊啊啊啊啊
aaaaaa aaaaa *(*aaaaa_aaaaa)(aaaaaa aaaaa_aaaaa
*aa);
aaaa (*aaaaaaa_aaaaa)(aaaaaa aaaaa *);
aaa (*aaaaa_aaaaa) (aaaaaa aaaaa *,
aaaaaa aaaaaaaaa_aaaaaaa *aaa);
...
// 啊啊啊啊啊啊啊啊啊啊啊
aaa (*aaaa_aa)(aaaaaa aaaaa_aaaaa *aa, aaa aaaa);
aaa (*aaaaaa) (aaaaaa aaaaaa *, aaaaaa aaaaaaa *);
aaa (*aaaaaaa_aa) (aaaaaa aaaaa_aaaaa *, aaa *,
aaaa *);
aaaa (*aaaaaa_aaaaa) (aaaaaa aaaaa_aaaaa *);
...
};
\aaa{aaaaaaaaaaaaa}
\aaaaaaa{aaaaa aaa啊啊啊啊啊啊啊啊啊啊啊啊}%
\aaaaa{aaaa:aa:aaa-aaaaa-aaa}
\aaa{aaaaaaa}
\aaaaaaaaa{啊啊啊啊啊啊啊啊啊啊。}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊~\aaa{aaaa:aa:aaa-aaaaa-aaa}啊啊啊啊aaaaa啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊\aaaaaaaa{aaaaa_aaaaa})、啊啊(\aaaaaaaa{aaaaaaa_aaaaa})啊啊啊(\aaaaaaaa{aaaaa_aaaaa})。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊(\aaaaaaaa{aaaa_aa})、啊啊(\aaaaaaaa{aaaaaa})、啊啊啊(\aaaaaaaa{aaaaaaa_aa})啊啊啊(\aaaaaaaa{aaaaaa_aaaaa})啊啊啊啊啊啊。
\aaaaa{aaaaaaaa}
啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊“啊啊”啊aaa啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaa{aaaaaaaa}
\aaaaa{aaaaaaa}[aa]
\aaaaa{aaaaaaaaaaaaa}
aaaaaa aaaa_aaaaaaaaaa {
aaaaaa aaaaaa *aaaaa;
aaaa_a (*aaaaaa) (aaaaaa aaaa *, aaaa_a, aaa);
aaaaa_a (*aaaa) (aaaaaa aaaa *, aaaa __aaaa *,
aaaa_a, aaaa_a *);
aaaaa_a (*aaaaa) (aaaaaa aaaa *,
aaaaa aaaa __aaaa *,
aaaa_a, aaaa_a *);
aaaaa_a (*aaaa_aaaa) (aaaaaa aaaaa *,
aaaaaa aaa_aaaa *);
aaaaa_a (*aaaaa_aaaa) (aaaaaa aaaaa *,
aaaaaa aaa_aaaa *);
aaaa (*aaaaaaaa_aaaaa) (aaaaaa aaaa *, aaaaaaaa aaa,
aaaaaaaa aaaa);
aaa (*aaaa) (aaaaaa aaaa *,
aaaaaa aa_aaaa_aaaaaa *);
aaa (*aaaa) (aaaaaa aaaaa *, aaaaaa aaaa *);
aaa (*aaaaa) (aaaaaa aaaa *, aaaa_a, aaaa_a,
aaa aaaaaaaa);
...
};
\aaa{aaaaaaaaaaaaa}
\aaaaaaa{aaaaa aaa啊啊啊啊啊啊啊啊啊啊}%
\aaaaa{aaaa:aa:aaa-aaaa-aaa}
\aaa{aaaaaaa}
\aaaaaaaaa{啊啊啊啊啊啊啊。}
啊啊啊啊~\aaa{aaaa:aa:aaa-aaaa-aaa}啊啊啊啊啊啊aaaaa啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{aaaa})、啊(\aaaaaaaa{aaaa}啊\aaaaaaaa{aaaa_aaaa})、啊(\aaaaaaaa{aaaaa}啊\aaaaaaaa{aaaaa_aaaa})、啊啊(\aaaaaaaa{aaaaaa})、啊啊啊啊(\aaaaaaaa{aaaa})、啊啊啊啊(\aaaaaaaa{aaaaa})啊啊啊。
\aaaaa{aaaaaaa-aaaa}[啊啊啊]
啊啊啊啊~\aaa{aaaa:aa:aaa-aaaa-aaa}啊啊\aaaaaaaa{aaaaaaaa_aaaaa}啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊aaa。
啊啊aaaaa啊啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊~\aaa{aaa:aaaaaaaaaaaaaa:aaa}啊啊啊啊啊啊啊),啊啊啊啊啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaa{aaaaaaa-aaaa}
\aaaaa{aaaaaaa}[aa]
\aaaaa{aaaaaaaaaaaaa}
aaaaaa aaaaa_aaaaaaaaaa {
aaaaaa aaaaaa * (*aaaaaa) (aaaaaa aaaaa *,
aaaaaa aaaaaa *,
aaaaaaaa aaa);
aaa (*aaaaaaaa) (aaaaaa aaaaaa *, aaaa __aaaa *,
aaa);
aaa (*aaaaaa) (aaaaaa aaaaa *, aaaaaa aaaaaa *,
aaaaa_a, aaaa);
aaa (*aaaa) (aaaaaa aaaaaa *, aaaaaa aaaaa *,
aaaaaa aaaaaa *);
aaa (*aaaaaa) (aaaaaa aaaaa *, aaaaaa aaaaaa *);
aaa (*aaaaaaa) (aaaaaa aaaaa *, aaaaaa aaaaaa *,
aaaaa aaaa *);
aaa (*aaaaa) (aaaaaa aaaaa *, aaaaaa aaaaaa *,
aaaaa_a);
aaa (*aaaaa) (aaaaaa aaaaa *, aaaaaa aaaaaa *);
aaa (*aaaaa) (aaaaaa aaaaa *, aaaaaa aaaaaa *,
aaaaa_a, aaa_a);
aaa (*aaaaaa) (aaaaaa aaaaa *, aaaaaa aaaaaa *,
aaaaaa aaaaa *, aaaaaa aaaaaa *,
aaaaaaaa aaa);
aaa (*aaaaaa_aaaa)(aaaaaa aaaaa *,
aaaaaa aaaaaaaa64 *, aaa);
...
};
\aaa{aaaaaaaaaaaaa}
\aaaaaaa{aaaaa aaa啊啊啊aaaaa啊啊啊啊啊}%
\aaaaa{aaaa:aa:aaa-aaaaa-aaa}
\aaa{aaaaaaa}
\aaaaaaaaa{aaaaa啊啊啊啊啊。}
啊啊啊啊~\aaa{aaaa:aa:aaa-aaaaa-aaa}啊啊啊啊啊啊aaaaa啊aaa啊啊啊啊啊啊aaaaa啊啊啊啊啊。
啊啊啊啊啊啊(\aaaaaaaa{aaaaaa})、啊啊啊(\aaaaaaaa{aaaa})、啊啊啊啊啊啊啊(\aaaaaaaa{aaaaaa})、啊啊啊啊啊啊(\aaaaaaaa{aaaaaaa})、啊啊啊啊啊啊啊啊啊(\aaaaaaaa{aaaaa}啊\aaaaaaaa{aaaaa})、啊啊啊(\aaaaaaaa{aaaaaa})、啊啊啊啊(\aaaaaaaa{aaaaaa_aaaa})啊啊啊。
aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊aaaaa啊aaa啊啊啊啊啊啊啊啊啊啊
\aaaaa{aaaaaaaa}
aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊
啊啊aaa啊啊啊啊。啊啊啊aaa啊啊啊啊啊啊“啊啊”。
\aaa{aaaaaaaa}
% \aaa{啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaaaa啊啊啊啊啊啊啊。啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。}
\aaaaaaaaaaaaa{啊啊啊啊}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
aaaaa啊aaa啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、aaaaa、啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊\aaaa{啊啊啊啊啊啊}啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊aaaaa啊啊aaa啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaa{aaa啊啊啊啊啊。}
aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊、啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaa{aaa啊啊aaaaa。}
aaaaa啊aaa啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊aaaaa啊啊啊啊。
啊啊啊啊啊啊啊啊啊aaaaaaaa啊啊啊啊啊aaaaa啊啊aaaaaa
aaa啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊aaaaa啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaa{aaa啊啊啊啊啊啊啊啊。}
啊啊aaa啊aaaaa啊啊啊啊啊啊aaaaa aaaa啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaa{aaa啊啊啊啊啊。}
%啊aaa啊啊啊啊啊啊啊啊啊aaaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaaa。
aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊。
啊啊aaaaa啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaaa
\aaaaa{aaaaaaaa}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?
\aaa{aaaaaaaa}
%\aaaaa{aaaaaaa-aaaa}[啊啊啊啊啊啊]
%
%aaaaa aaa啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%\aaa{aaaaaaa-aaaa}
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊“啊啊—啊啊—啊啊”aaaa-aaaaaa-aaaaa啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaaaa}
啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊
啊啊啊啊啊啊啊?
\aaa{aaaaaaaa}
\aaaaaaaaaa{啊啊啊啊啊啊啊啊啊}
% % aaaaaaa(aa): 啊啊aa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
% % 啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊?啊啊啊啊啊啊啊啊啊,
% % 啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aa
啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊aaaaa~\aaaa{aaaaa-1-2017},啊啊啊啊啊啊啊啊(啊啊啊啊啊啊啊啊)啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaaaaaa{啊啊啊啊啊啊啊啊啊啊啊}
%啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
%啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaa{aaaaaaa}[aaa]
\aaaaa{aaaaaaaaaaaaa}
#aaaaaaa <aaaaa.a>
#aaaaaaa <aaaaa.a>
#aaaaaaa <aaaaaa.a>
#aaaaaa aaaa_aaaa 20
aaa aaaa()
{
aaa aa;
aaaa aaaa[aaaa_aaaa + 1];
// 啊啊啊啊
aa = aaaa("/aaaa/aaaaaa/aaaaaaaaaa.aaa",
a_aaaa | a_aaaaa);
// 啊啊啊啊啊啊啊20啊啊啊
aaaa(aa, aaaa, aaaa_aaaa);
// 啊啊啊啊啊啊啊20啊啊啊
aaaa[aaaa_aaaa] = '\0';
aaaaaa("aaaa aaaa: %a\a", aaaa);
// 啊啊啊啊啊啊6啊啊啊啊啊啊
aaaaa(aa, "aaaaa\a", 6);
// 啊啊啊啊
aaaaa(aa);
aaaaaa 0;
}
\aaa{aaaaaaaaaaaaa}
\aaaaaaa{啊啊啊啊啊啊啊啊啊啊}%
\aaaaa{aaaa:aa:aaaaaa-aaaaaaa}
\aaa{aaaaaaa}
啊啊啊啊~\aaa{aaaa:aa:aaaaaa-aaaaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊~\aaa{aaaa:aa:aaaaaaaaaaaaa}啊啊啊啊啊~\aaa{aaaa:aa:aaaaaaa-aaaaaaaaaa}啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊~\aaa{aaaa:aa:aaaaaa-aaaaaaa}啊啊啊啊啊啊啊啊\aaaaaaaa{aaaa}啊啊啊啊啊啊“/aaaa/aaaaaa/aaaaaaaaaa.aaa”啊啊啊。
啊啊啊啊啊啊\aaaaaaaa{a_aaaa|a_aaaaa}。
啊啊\aaaaaaaa{a_aaaa}啊啊啊啊啊啊啊啊啊(啊啊啊啊啊啊啊啊啊啊啊啊啊);
\aaaaaaaa{a_aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊,\aaaaaaaa{aaaa}啊啊啊啊啊啊啊\aaaaaa{啊啊啊啊啊}。
啊啊啊啊啊啊啊啊啊\aaaaaaaa{aa}啊啊啊啊。
啊啊,啊啊啊\aaaaaaaa{aa}啊啊啊啊啊啊\aaaaaaaa{aaaa}啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊\aaaaaaaa{aaaa}啊啊啊啊啊,啊啊啊啊啊啊啊啊\aaaaaaaa{20}啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{aaaa}啊啊啊啊啊啊啊。
啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊,啊啊啊啊啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊啊啊啊\aaaaaaaa{6}啊啊啊,啊啊啊啊\aaaaaaaa{aaaaa}啊啊啊啊啊啊(\aaaaaaaa{\a})。
啊啊,啊啊啊啊啊啊。
啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaaaaaa{啊啊啊啊}
啊啊啊啊啊啊啊啊啊,啊啊啊啊啊\aaaaaaaa{aaaa}啊\aaaa{啊啊啊啊}啊啊啊啊啊啊\aaaa{啊啊啊啊啊},啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊\aaaaaaaa{aaaa}啊啊啊aaaa啊啊啊啊啊啊\aaaaaaaa{aaa_aaaa}啊啊啊啊啊啊啊啊啊aaa啊啊啊啊。
aaa啊啊啊\aaaaaaaa{aaa_aaaa}啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊。
啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊~\aaa{aaaa:aa:aaaaaa-aaaaaaa}啊,啊啊“/aaaa/aaaaaa/aaaaaaaaaa.aaa”啊啊啊啊“aaaa”、“aaaaaa”啊“aaaaaaaaaa.aaa”。
啊啊啊啊啊啊“/”啊啊啊\aaaaaa{啊啊啊啊},啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
aaa啊啊啊啊啊啊啊啊啊啊啊“aaaa”啊啊啊。
啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊“aaaa”啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊、啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊“aaaa”啊啊啊啊啊啊啊“aaaaaa”啊啊。
啊啊啊啊啊啊啊,啊啊啊啊啊。
啊啊啊啊啊啊啊\aaaaaaaa{aaaa}啊啊啊啊\aaaaaaaa{a_aaaaa}啊啊啊啊啊啊啊啊啊啊啊啊啊“aaaaaaaaaa.aaa”啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊aaa啊啊啊“aaaaaaaaaa.aaa”啊啊啊啊啊啊aaaaa。
啊啊aaa啊啊啊啊啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
\aaaaaaaaaaaaa{啊啊啊啊啊}
\aaaaa{aaaaaa}[aaa]
\aaaaaaaaa
\aaaaaaaaaaaaaaa[aaaaa=0.6\aaaaaaaaa]{aaaa/aaaa/aa/aaa-aa.aaa} \\[1aa]
\aaaaaaa{啊啊啊啊啊}%
\aaaaa{aaa:aa:aaa-aa}
\aaa{aaaaaa}
%啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊\aaaaaaaa{aa}啊啊\aaaaaaaa{aaaaaa}啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊。
啊啊~\aaa{aaa:aa:aaa-aa}aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊啊啊啊啊啊aaaaa、啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaaaa啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
aaa啊aaaaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊0啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊(啊\aaaaaaaa{aaaa}啊\aaaaaaaa{aaaaa}aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊啊啊\aaaa{啊啊啊啊}啊啊\aaaa{啊啊啊啊啊},啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊啊啊啊啊aaa啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊。
啊啊,啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊啊,啊啊啊啊啊啊啊啊啊啊。

File diff suppressed because one or more lines are too long

View File

@@ -374,13 +374,6 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
return null;
}
return themeData.ensureLoaded(this.extensionResourceLoaderService).then(_ => {
if (themeId === this.currentColorTheme.id && !this.currentColorTheme.isLoaded && this.currentColorTheme.hasEqualData(themeData)) {
this.currentColorTheme.clearCaches();
// the loaded theme is identical to the perisisted theme. Don't need to send an event.
this.currentColorTheme = themeData;
themeData.setCustomizations(this.settings);
return Promise.resolve(themeData);
}
themeData.setCustomizations(this.settings);
return this.applyTheme(themeData, settingsTarget);
}, error => {

View File

@@ -0,0 +1,446 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as perf from 'vs/base/common/performance';
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { IUpdateService } from 'vs/platform/update/common/update';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
/* __GDPR__FRAGMENT__
"IMemoryInfo" : {
"workingSetSize" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"privateBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"sharedBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
export interface IMemoryInfo {
readonly workingSetSize: number;
readonly privateBytes: number;
readonly sharedBytes: number;
}
/* __GDPR__FRAGMENT__
"IStartupMetrics" : {
"version" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"ellapsed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"isLatestVersion": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"didUseCachedData": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"windowKind": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"windowCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"viewletId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"panelId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"editorIds": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"timers.ellapsedAppReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWindowLoad" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWindowLoadToRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkspaceStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkspaceServiceInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedPanelRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedEditorRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkbench" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedTimersToTimersComputed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedNlsGeneration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"platform" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"release" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"arch" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"totalmem" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"freemem" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"meminfo" : { "${inline}": [ "${IMemoryInfo}" ] },
"cpus.count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cpus.speed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cpus.model" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"initialStartup" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"hasAccessibilitySupport" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"isVMLikelyhood" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"emptyWorkbench" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"loadavg" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
export interface IStartupMetrics {
/**
* The version of these metrics.
*/
readonly version: 2;
/**
* If this started the main process and renderer or just a renderer (new or reloaded).
*/
readonly initialStartup: boolean;
/**
* No folder, no file, no workspace has been opened
*/
readonly emptyWorkbench: boolean;
/**
* This is the latest (stable/insider) version. Iff not we should ignore this
* measurement.
*/
readonly isLatestVersion: boolean;
/**
* Whether we asked for and V8 accepted cached data.
*/
readonly didUseCachedData: boolean;
/**
* How/why the window was created. See https://github.com/Microsoft/vscode/blob/d1f57d871722f4d6ba63e4ef6f06287121ceb045/src/vs/platform/lifecycle/common/lifecycle.ts#L50
*/
readonly windowKind: number;
/**
* The total number of windows that have been restored/created
*/
readonly windowCount: number;
/**
* The active viewlet id or `undedined`
*/
readonly viewletId?: string;
/**
* The active panel id or `undefined`
*/
readonly panelId?: string;
/**
* The editor input types or `[]`
*/
readonly editorIds: string[];
/**
* The time it took to create the workbench.
*
* * Happens in the main-process *and* the renderer-process
* * Measured with the *start* and `didStartWorkbench`-performance mark. The *start* is either the start of the
* main process or the start of the renderer.
* * This should be looked at carefully because times vary depending on
* * This being the first window, the only window, or a reloaded window
* * Cached data being present and used or not
* * The numbers and types of editors being restored
* * The numbers of windows being restored (when starting 'fresh')
* * The viewlet being restored (esp. when it's a contributed viewlet)
*/
readonly ellapsed: number;
/**
* Individual timers...
*/
readonly timers: {
/**
* The time it took to receieve the [`ready`](https://electronjs.org/docs/api/app#event-ready)-event. Measured from the first line
* of JavaScript code till receiving that event.
*
* * Happens in the main-process
* * Measured with the `main:started` and `main:appReady` performance marks.
* * This can be compared between insider and stable builds.
* * This should be looked at per OS version and per electron version.
* * This is often affected by AV software (and can change with AV software updates outside of our release-cycle).
* * It is not our code running here and we can only observe what's happening.
*/
readonly ellapsedAppReady?: number;
/**
* The time it took to generate NLS data.
*
* * Happens in the main-process
* * Measured with the `nlsGeneration:start` and `nlsGeneration:end` performance marks.
* * This only happens when a non-english locale is being used.
* * It is our code running here and we should monitor this carefully for regressions.
*/
readonly ellapsedNlsGeneration?: number;
/**
* The time it took to tell electron to open/restore a renderer (browser window).
*
* * Happens in the main-process
* * Measured with the `main:appReady` and `main:loadWindow` performance marks.
* * This can be compared between insider and stable builds.
* * It is our code running here and we should monitor this carefully for regressions.
*/
readonly ellapsedWindowLoad?: number;
/**
* The time it took to create a new renderer (browser window) and to initialize that to the point
* of load the main-bundle (`workbench.desktop.main.js`).
*
* * Happens in the main-process *and* the renderer-process
* * Measured with the `main:loadWindow` and `willLoadWorkbenchMain` performance marks.
* * This can be compared between insider and stable builds.
* * It is mostly not our code running here and we can only observe what's happening.
*
*/
readonly ellapsedWindowLoadToRequire: number;
/**
* The time it took to require the workspace storage DB, connect to it
* and load the initial set of values.
*
* * Happens in the renderer-process
* * Measured with the `willInitWorkspaceStorage` and `didInitWorkspaceStorage` performance marks.
*/
readonly ellapsedWorkspaceStorageInit: number;
/**
* The time it took to initialize the workspace and configuration service.
*
* * Happens in the renderer-process
* * Measured with the `willInitWorkspaceService` and `didInitWorkspaceService` performance marks.
*/
readonly ellapsedWorkspaceServiceInit: number;
/**
* The time it took to load the main-bundle of the workbench, e.g. `workbench.desktop.main.js`.
*
* * Happens in the renderer-process
* * Measured with the `willLoadWorkbenchMain` and `didLoadWorkbenchMain` performance marks.
* * This varies *a lot* when V8 cached data could be used or not
* * This should be looked at with and without V8 cached data usage and per electron/v8 version
* * This is affected by the size of our code bundle (which grows about 3-5% per release)
*/
readonly ellapsedRequire: number;
/**
* The time it took to read extensions' package.json-files *and* interpret them (invoking
* the contribution points).
*
* * Happens in the renderer-process
* * Measured with the `willLoadExtensions` and `didLoadExtensions` performance marks.
* * Reading of package.json-files is avoided by caching them all in a single file (after the read,
* until another extension is installed)
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedExtensions: number;
// the time from start till `didLoadExtensions`
// remove?
readonly ellapsedExtensionsReady: number;
/**
* The time it took to restore the viewlet.
*
* * Happens in the renderer-process
* * Measured with the `willRestoreViewlet` and `didRestoreViewlet` performance marks.
* * This should be looked at per viewlet-type/id.
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedViewletRestore: number;
/**
* The time it took to restore the panel.
*
* * Happens in the renderer-process
* * Measured with the `willRestorePanel` and `didRestorePanel` performance marks.
* * This should be looked at per panel-type/id.
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedPanelRestore: number;
/**
* The time it took to restore editors - that is text editor and complex editor likes the settings UI
* or webviews (markdown preview).
*
* * Happens in the renderer-process
* * Measured with the `willRestoreEditors` and `didRestoreEditors` performance marks.
* * This should be looked at per editor and per editor type.
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedEditorRestore: number;
/**
* The time it took to create the workbench.
*
* * Happens in the renderer-process
* * Measured with the `willStartWorkbench` and `didStartWorkbench` performance marks.
*/
readonly ellapsedWorkbench: number;
/**
* This time it took inside the renderer to start the workbench.
*
* * Happens in the renderer-process
* * Measured with the `renderer/started` and `didStartWorkbench` performance marks
*/
readonly ellapsedRenderer: number;
// the time it took to generate this object.
// remove?
readonly ellapsedTimersToTimersComputed: number;
};
readonly hasAccessibilitySupport: boolean;
readonly isVMLikelyhood?: number;
readonly platform?: string;
readonly release?: string;
readonly arch?: string;
readonly totalmem?: number;
readonly freemem?: number;
readonly meminfo?: IMemoryInfo;
readonly cpus?: { count: number; speed: number; model: string; };
readonly loadavg?: number[];
}
export interface ITimerService {
readonly _serviceBrand: undefined;
readonly startupMetrics: Promise<IStartupMetrics>;
}
export const ITimerService = createDecorator<ITimerService>('timerService');
export type Writeable<T> = { -readonly [P in keyof T]: Writeable<T[P]> };
export abstract class AbstractTimerService implements ITimerService {
declare readonly _serviceBrand: undefined;
private _startupMetrics?: Promise<IStartupMetrics>;
constructor(
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IExtensionService private readonly _extensionService: IExtensionService,
@IUpdateService private readonly _updateService: IUpdateService,
@IViewletService private readonly _viewletService: IViewletService,
@IPanelService private readonly _panelService: IPanelService,
@IEditorService private readonly _editorService: IEditorService,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService,
@ITelemetryService private readonly _telemetryService: ITelemetryService,
) { }
get startupMetrics(): Promise<IStartupMetrics> {
if (!this._startupMetrics) {
this._startupMetrics = this._extensionService.whenInstalledExtensionsRegistered()
.then(() => this._computeStartupMetrics())
.then(metrics => {
this._reportStartupTimes(metrics);
return metrics;
});
}
return this._startupMetrics;
}
private _reportStartupTimes(metrics: IStartupMetrics): void {
// report IStartupMetrics as telemetry
/* __GDPR__
"startupTimeVaried" : {
"${include}": [
"${IStartupMetrics}"
]
}
*/
this._telemetryService.publicLog('startupTimeVaried', metrics);
// report raw timers as telemetry
const entries: Record<string, number> = Object.create(null);
for (const entry of perf.getEntries()) {
entries[entry.name] = entry.startTime;
}
/* __GDPR__
"startupRawTimers" : {
"entries": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
this._telemetryService.publicLog('startupRawTimers', { entries });
}
private async _computeStartupMetrics(): Promise<IStartupMetrics> {
const now = Date.now();
const initialStartup = this._isInitialStartup();
const startMark = initialStartup ? 'main:started' : 'main:loadWindow';
const activeViewlet = this._viewletService.getActiveViewlet();
const activePanel = this._panelService.getActivePanel();
const info: Writeable<IStartupMetrics> = {
version: 2,
ellapsed: perf.getDuration(startMark, 'didStartWorkbench'),
// reflections
isLatestVersion: Boolean(await this._updateService.isLatestVersion()),
didUseCachedData: this._didUseCachedData(),
windowKind: this._lifecycleService.startupKind,
windowCount: await this._getWindowCount(),
viewletId: activeViewlet?.getId(),
editorIds: this._editorService.visibleEditors.map(input => input.getTypeId()),
panelId: activePanel ? activePanel.getId() : undefined,
// timers
timers: {
ellapsedAppReady: initialStartup ? perf.getDuration('main:started', 'main:appReady') : undefined,
ellapsedNlsGeneration: initialStartup ? perf.getDuration('nlsGeneration:start', 'nlsGeneration:end') : undefined,
ellapsedWindowLoad: initialStartup ? perf.getDuration('main:appReady', 'main:loadWindow') : undefined,
ellapsedWindowLoadToRequire: perf.getDuration('main:loadWindow', 'willLoadWorkbenchMain'),
ellapsedRequire: perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'),
ellapsedWorkspaceStorageInit: perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage'),
ellapsedWorkspaceServiceInit: perf.getDuration('willInitWorkspaceService', 'didInitWorkspaceService'),
ellapsedExtensions: perf.getDuration('willLoadExtensions', 'didLoadExtensions'),
ellapsedEditorRestore: perf.getDuration('willRestoreEditors', 'didRestoreEditors'),
ellapsedViewletRestore: perf.getDuration('willRestoreViewlet', 'didRestoreViewlet'),
ellapsedPanelRestore: perf.getDuration('willRestorePanel', 'didRestorePanel'),
ellapsedWorkbench: perf.getDuration('willStartWorkbench', 'didStartWorkbench'),
ellapsedExtensionsReady: perf.getDuration(startMark, 'didLoadExtensions'),
ellapsedRenderer: perf.getDuration('renderer/started', 'didStartWorkbench'),
ellapsedTimersToTimersComputed: Date.now() - now,
},
// system info
platform: undefined,
release: undefined,
arch: undefined,
totalmem: undefined,
freemem: undefined,
meminfo: undefined,
cpus: undefined,
loadavg: undefined,
isVMLikelyhood: undefined,
initialStartup,
hasAccessibilitySupport: this._accessibilityService.isScreenReaderOptimized(),
emptyWorkbench: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY
};
await this._extendStartupInfo(info);
return info;
}
protected abstract _isInitialStartup(): boolean;
protected abstract _didUseCachedData(): boolean;
protected abstract _getWindowCount(): Promise<number>;
protected abstract _extendStartupInfo(info: Writeable<IStartupMetrics>): Promise<void>;
}
export class TimerService extends AbstractTimerService {
protected _isInitialStartup(): boolean {
return false;
}
protected _didUseCachedData(): boolean {
return false;
}
protected async _getWindowCount(): Promise<number> {
return 1;
}
protected async _extendStartupInfo(info: Writeable<IStartupMetrics>): Promise<void> {
info.isVMLikelyhood = 0;
info.platform = navigator.userAgent;
info.release = navigator.appVersion;
}
}

View File

@@ -3,15 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { virtualMachineHint } from 'vs/base/node/id';
import * as perf from 'vs/base/common/performance';
import * as os from 'os';
import { IElectronService } from 'vs/platform/electron/electron-sandbox/electron';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IUpdateService } from 'vs/platform/update/common/update';
import { ILifecycleService } from 'vs/platform/lifecycle/common/lifecycle';
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
@@ -19,406 +16,65 @@ import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IAccessibilityService } from 'vs/platform/accessibility/common/accessibility';
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
import { IStartupMetrics, AbstractTimerService, Writeable } from 'vs/workbench/services/timer/browser/timerService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
/* __GDPR__FRAGMENT__
"IMemoryInfo" : {
"workingSetSize" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"privateBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"sharedBytes": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
export interface IMemoryInfo {
readonly workingSetSize: number;
readonly privateBytes: number;
readonly sharedBytes: number;
}
/* __GDPR__FRAGMENT__
"IStartupMetrics" : {
"version" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"ellapsed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"isLatestVersion": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"didUseCachedData": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"windowKind": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"windowCount": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"viewletId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"panelId": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"editorIds": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"timers.ellapsedAppReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWindowLoad" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWindowLoadToRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedExtensions" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedExtensionsReady" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedRequire" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkspaceStorageInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkspaceServiceInit" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedViewletRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedPanelRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedEditorRestore" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedWorkbench" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedTimersToTimersComputed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"timers.ellapsedNlsGeneration" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"platform" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"release" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"arch" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"totalmem" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"freemem" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"meminfo" : { "${inline}": [ "${IMemoryInfo}" ] },
"cpus.count" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cpus.speed" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"cpus.model" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" },
"initialStartup" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"hasAccessibilitySupport" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"isVMLikelyhood" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"emptyWorkbench" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"loadavg" : { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
*/
export interface IStartupMetrics {
/**
* The version of these metrics.
*/
readonly version: 2;
/**
* If this started the main process and renderer or just a renderer (new or reloaded).
*/
readonly initialStartup: boolean;
/**
* No folder, no file, no workspace has been opened
*/
readonly emptyWorkbench: boolean;
/**
* This is the latest (stable/insider) version. Iff not we should ignore this
* measurement.
*/
readonly isLatestVersion: boolean;
/**
* Whether we asked for and V8 accepted cached data.
*/
readonly didUseCachedData: boolean;
/**
* How/why the window was created. See https://github.com/Microsoft/vscode/blob/d1f57d871722f4d6ba63e4ef6f06287121ceb045/src/vs/platform/lifecycle/common/lifecycle.ts#L50
*/
readonly windowKind: number;
/**
* The total number of windows that have been restored/created
*/
readonly windowCount: number;
/**
* The active viewlet id or `undedined`
*/
readonly viewletId?: string;
/**
* The active panel id or `undefined`
*/
readonly panelId?: string;
/**
* The editor input types or `[]`
*/
readonly editorIds: string[];
/**
* The time it took to create the workbench.
*
* * Happens in the main-process *and* the renderer-process
* * Measured with the *start* and `didStartWorkbench`-performance mark. The *start* is either the start of the
* main process or the start of the renderer.
* * This should be looked at carefully because times vary depending on
* * This being the first window, the only window, or a reloaded window
* * Cached data being present and used or not
* * The numbers and types of editors being restored
* * The numbers of windows being restored (when starting 'fresh')
* * The viewlet being restored (esp. when it's a contributed viewlet)
*/
readonly ellapsed: number;
/**
* Individual timers...
*/
readonly timers: {
/**
* The time it took to receieve the [`ready`](https://electronjs.org/docs/api/app#event-ready)-event. Measured from the first line
* of JavaScript code till receiving that event.
*
* * Happens in the main-process
* * Measured with the `main:started` and `main:appReady` performance marks.
* * This can be compared between insider and stable builds.
* * This should be looked at per OS version and per electron version.
* * This is often affected by AV software (and can change with AV software updates outside of our release-cycle).
* * It is not our code running here and we can only observe what's happening.
*/
readonly ellapsedAppReady?: number;
/**
* The time it took to generate NLS data.
*
* * Happens in the main-process
* * Measured with the `nlsGeneration:start` and `nlsGeneration:end` performance marks.
* * This only happens when a non-english locale is being used.
* * It is our code running here and we should monitor this carefully for regressions.
*/
readonly ellapsedNlsGeneration?: number;
/**
* The time it took to tell electron to open/restore a renderer (browser window).
*
* * Happens in the main-process
* * Measured with the `main:appReady` and `main:loadWindow` performance marks.
* * This can be compared between insider and stable builds.
* * It is our code running here and we should monitor this carefully for regressions.
*/
readonly ellapsedWindowLoad?: number;
/**
* The time it took to create a new renderer (browser window) and to initialize that to the point
* of load the main-bundle (`workbench.desktop.main.js`).
*
* * Happens in the main-process *and* the renderer-process
* * Measured with the `main:loadWindow` and `willLoadWorkbenchMain` performance marks.
* * This can be compared between insider and stable builds.
* * It is mostly not our code running here and we can only observe what's happening.
*
*/
readonly ellapsedWindowLoadToRequire: number;
/**
* The time it took to require the workspace storage DB, connect to it
* and load the initial set of values.
*
* * Happens in the renderer-process
* * Measured with the `willInitWorkspaceStorage` and `didInitWorkspaceStorage` performance marks.
*/
readonly ellapsedWorkspaceStorageInit: number;
/**
* The time it took to initialize the workspace and configuration service.
*
* * Happens in the renderer-process
* * Measured with the `willInitWorkspaceService` and `didInitWorkspaceService` performance marks.
*/
readonly ellapsedWorkspaceServiceInit: number;
/**
* The time it took to load the main-bundle of the workbench, e.g. `workbench.desktop.main.js`.
*
* * Happens in the renderer-process
* * Measured with the `willLoadWorkbenchMain` and `didLoadWorkbenchMain` performance marks.
* * This varies *a lot* when V8 cached data could be used or not
* * This should be looked at with and without V8 cached data usage and per electron/v8 version
* * This is affected by the size of our code bundle (which grows about 3-5% per release)
*/
readonly ellapsedRequire: number;
/**
* The time it took to read extensions' package.json-files *and* interpret them (invoking
* the contribution points).
*
* * Happens in the renderer-process
* * Measured with the `willLoadExtensions` and `didLoadExtensions` performance marks.
* * Reading of package.json-files is avoided by caching them all in a single file (after the read,
* until another extension is installed)
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedExtensions: number;
// the time from start till `didLoadExtensions`
// remove?
readonly ellapsedExtensionsReady: number;
/**
* The time it took to restore the viewlet.
*
* * Happens in the renderer-process
* * Measured with the `willRestoreViewlet` and `didRestoreViewlet` performance marks.
* * This should be looked at per viewlet-type/id.
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedViewletRestore: number;
/**
* The time it took to restore the panel.
*
* * Happens in the renderer-process
* * Measured with the `willRestorePanel` and `didRestorePanel` performance marks.
* * This should be looked at per panel-type/id.
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedPanelRestore: number;
/**
* The time it took to restore editors - that is text editor and complex editor likes the settings UI
* or webviews (markdown preview).
*
* * Happens in the renderer-process
* * Measured with the `willRestoreEditors` and `didRestoreEditors` performance marks.
* * This should be looked at per editor and per editor type.
* * Happens in parallel to other things, depends on async timing
*/
readonly ellapsedEditorRestore: number;
/**
* The time it took to create the workbench.
*
* * Happens in the renderer-process
* * Measured with the `willStartWorkbench` and `didStartWorkbench` performance marks.
*/
readonly ellapsedWorkbench: number;
// the time it took to generate this object.
// remove?
readonly ellapsedTimersToTimersComputed: number;
};
readonly hasAccessibilitySupport: boolean;
readonly isVMLikelyhood?: number;
readonly platform?: string;
readonly release?: string;
readonly arch?: string;
readonly totalmem?: number;
readonly freemem?: number;
readonly meminfo?: IMemoryInfo;
readonly cpus?: { count: number; speed: number; model: string; };
readonly loadavg?: number[];
}
export interface ITimerService {
readonly _serviceBrand: undefined;
readonly startupMetrics: Promise<IStartupMetrics>;
}
class TimerService implements ITimerService {
declare readonly _serviceBrand: undefined;
private _startupMetrics?: Promise<IStartupMetrics>;
export class TimerService extends AbstractTimerService {
constructor(
@IElectronService private readonly _electronService: IElectronService,
@IWorkbenchEnvironmentService private readonly _environmentService: INativeWorkbenchEnvironmentService,
@ILifecycleService private readonly _lifecycleService: ILifecycleService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@IExtensionService private readonly _extensionService: IExtensionService,
@IUpdateService private readonly _updateService: IUpdateService,
@IViewletService private readonly _viewletService: IViewletService,
@IPanelService private readonly _panelService: IPanelService,
@IEditorService private readonly _editorService: IEditorService,
@IAccessibilityService private readonly _accessibilityService: IAccessibilityService
) { }
get startupMetrics(): Promise<IStartupMetrics> {
if (!this._startupMetrics) {
this._startupMetrics = Promise
.resolve(this._extensionService.whenInstalledExtensionsRegistered())
.then(() => this._computeStartupMetrics());
}
return this._startupMetrics;
@ILifecycleService lifecycleService: ILifecycleService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IExtensionService extensionService: IExtensionService,
@IUpdateService updateService: IUpdateService,
@IViewletService viewletService: IViewletService,
@IPanelService panelService: IPanelService,
@IEditorService editorService: IEditorService,
@IAccessibilityService accessibilityService: IAccessibilityService,
@ITelemetryService telemetryService: ITelemetryService,
) {
super(lifecycleService, contextService, extensionService, updateService, viewletService, panelService, editorService, accessibilityService, telemetryService);
}
private async _computeStartupMetrics(): Promise<IStartupMetrics> {
const now = Date.now();
const initialStartup = !!this._environmentService.configuration.isInitialStartup;
const startMark = initialStartup ? 'main:started' : 'main:loadWindow';
let totalmem: number | undefined;
let freemem: number | undefined;
let cpus: { count: number; speed: number; model: string; } | undefined;
let platform: string | undefined;
let release: string | undefined;
let arch: string | undefined;
let loadavg: number[] | undefined;
let meminfo: IMemoryInfo | undefined;
let isVMLikelyhood: number | undefined;
protected _isInitialStartup(): boolean {
return Boolean(this._environmentService.configuration.isInitialStartup);
}
protected _didUseCachedData(): boolean {
return didUseCachedData();
}
protected _getWindowCount(): Promise<number> {
return this._electronService.getWindowCount();
}
protected async _extendStartupInfo(info: Writeable<IStartupMetrics>): Promise<void> {
try {
totalmem = os.totalmem();
freemem = os.freemem();
platform = os.platform();
release = os.release();
arch = os.arch();
loadavg = os.loadavg();
info.totalmem = os.totalmem();
info.freemem = os.freemem();
info.platform = os.platform();
info.release = os.release();
info.arch = os.arch();
info.loadavg = os.loadavg();
const processMemoryInfo = await process.getProcessMemoryInfo();
meminfo = {
info.meminfo = {
workingSetSize: processMemoryInfo.residentSet,
privateBytes: processMemoryInfo.private,
sharedBytes: processMemoryInfo.shared
};
isVMLikelyhood = Math.round((virtualMachineHint.value() * 100));
info.isVMLikelyhood = Math.round((virtualMachineHint.value() * 100));
const rawCpus = os.cpus();
if (rawCpus && rawCpus.length > 0) {
cpus = { count: rawCpus.length, speed: rawCpus[0].speed, model: rawCpus[0].model };
info.cpus = { count: rawCpus.length, speed: rawCpus[0].speed, model: rawCpus[0].model };
}
} catch (error) {
// ignore, be on the safe side with these hardware method calls
}
const activeViewlet = this._viewletService.getActiveViewlet();
const activePanel = this._panelService.getActivePanel();
return {
version: 2,
ellapsed: perf.getDuration(startMark, 'didStartWorkbench'),
// reflections
isLatestVersion: Boolean(await this._updateService.isLatestVersion()),
didUseCachedData: didUseCachedData(),
windowKind: this._lifecycleService.startupKind,
windowCount: await this._electronService.getWindowCount(),
viewletId: activeViewlet ? activeViewlet.getId() : undefined,
editorIds: this._editorService.visibleEditors.map(input => input.getTypeId()),
panelId: activePanel ? activePanel.getId() : undefined,
// timers
timers: {
ellapsedAppReady: initialStartup ? perf.getDuration('main:started', 'main:appReady') : undefined,
ellapsedNlsGeneration: initialStartup ? perf.getDuration('nlsGeneration:start', 'nlsGeneration:end') : undefined,
ellapsedWindowLoad: initialStartup ? perf.getDuration('main:appReady', 'main:loadWindow') : undefined,
ellapsedWindowLoadToRequire: perf.getDuration('main:loadWindow', 'willLoadWorkbenchMain'),
ellapsedRequire: perf.getDuration('willLoadWorkbenchMain', 'didLoadWorkbenchMain'),
ellapsedWorkspaceStorageInit: perf.getDuration('willInitWorkspaceStorage', 'didInitWorkspaceStorage'),
ellapsedWorkspaceServiceInit: perf.getDuration('willInitWorkspaceService', 'didInitWorkspaceService'),
ellapsedExtensions: perf.getDuration('willLoadExtensions', 'didLoadExtensions'),
ellapsedEditorRestore: perf.getDuration('willRestoreEditors', 'didRestoreEditors'),
ellapsedViewletRestore: perf.getDuration('willRestoreViewlet', 'didRestoreViewlet'),
ellapsedPanelRestore: perf.getDuration('willRestorePanel', 'didRestorePanel'),
ellapsedWorkbench: perf.getDuration('willStartWorkbench', 'didStartWorkbench'),
ellapsedExtensionsReady: perf.getDuration(startMark, 'didLoadExtensions'),
ellapsedTimersToTimersComputed: Date.now() - now,
},
// system info
platform,
release,
arch,
totalmem,
freemem,
meminfo,
cpus,
loadavg,
initialStartup,
isVMLikelyhood,
hasAccessibilitySupport: this._accessibilityService.isScreenReaderOptimized(),
emptyWorkbench: this._contextService.getWorkbenchState() === WorkbenchState.EMPTY
};
}
}
export const ITimerService = createDecorator<ITimerService>('timerService');
registerSingleton(ITimerService, TimerService, true);
//#region cached data logic
export function didUseCachedData(): boolean {

View File

@@ -7,7 +7,6 @@ import { Event, Emitter } from 'vs/base/common/event';
import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
import { IFileSystemProviderWithFileReadWriteCapability, IFileChange, IWatchOptions, IStat, FileOverwriteOptions, FileType, FileWriteOptions, FileDeleteOptions, FileSystemProviderCapabilities, IFileSystemProviderWithOpenReadWriteCloseCapability, FileOpenOptions, hasReadWriteCapability, hasOpenReadWriteCloseCapability, IFileSystemProviderWithFileReadStreamCapability, FileReadStreamOptions, hasFileReadStreamCapability } from 'vs/platform/files/common/files';
import { URI } from 'vs/base/common/uri';
import { startsWith } from 'vs/base/common/strings';
import { BACKUPS } from 'vs/platform/environment/common/environment';
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
import { CancellationToken } from 'vs/base/common/cancellation';
@@ -141,7 +140,7 @@ export class FileUserDataProvider extends Disposable implements
private toFileSystemResource(userDataResource: URI): URI {
const relativePath = this.extUri.relativePath(this.userDataHome, userDataResource)!;
if (startsWith(relativePath, BACKUPS)) {
if (relativePath.startsWith(BACKUPS)) {
return this.extUri.joinPath(this.extUri.dirname(this.fileSystemBackupsHome), relativePath);
}
return this.extUri.joinPath(this.fileSystemUserDataHome, relativePath);

View File

@@ -3,15 +3,14 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IAuthenticationProvider, getUserDataSyncStore, isAuthenticationProvider, IUserDataAutoSyncService, SyncResource, IResourcePreview, ISyncResourcePreview, Change, IManualSyncTask } from 'vs/platform/userDataSync/common/userDataSync';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNCED_DATA_COMMAND_ID, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel } from 'vs/workbench/services/userDataSync/common/userDataSync';
import { IUserDataSyncWorkbenchService, IUserDataSyncAccount, AccountStatus, CONTEXT_SYNC_ENABLEMENT, CONTEXT_SYNC_STATE, CONTEXT_ACCOUNT_STATE, SHOW_SYNC_LOG_COMMAND_ID, getSyncAreaLabel, IUserDataSyncPreview, IUserDataSyncResourceGroup, CONTEXT_ENABLE_MANUAL_SYNC_VIEW, MANUAL_SYNC_VIEW_ID, CONTEXT_ENABLE_ACTIVITY_VIEWS, SYNC_VIEW_CONTAINER_ID } from 'vs/workbench/services/userDataSync/common/userDataSync';
import { AuthenticationSession, AuthenticationSessionsChangeEvent } from 'vs/editor/common/modes';
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
import { flatten } from 'vs/base/common/arrays';
import { values } from 'vs/base/common/map';
import { flatten, equals } from 'vs/base/common/arrays';
import { IAuthenticationService } from 'vs/workbench/services/authentication/browser/authenticationService';
import { IUserDataSyncAccountService } from 'vs/platform/userDataSync/common/userDataSyncAccount';
import { IQuickInputService, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
@@ -26,9 +25,11 @@ import { canceled } from 'vs/base/common/errors';
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { Action } from 'vs/base/common/actions';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { isEqual } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { IViewsService, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
type UserAccountClassification = {
id: { classification: 'EndUserPseudonymizedInformation', purpose: 'BusinessInsight' };
@@ -42,6 +43,8 @@ type UserAccountEvent = {
id: string;
};
type FirstTimeSyncAction = 'pull' | 'push' | 'merge' | 'manual';
type AccountQuickPickItem = { label: string, authenticationProvider: IAuthenticationProvider, account?: UserDataSyncAccount, description?: string };
class UserDataSyncAccount implements IUserDataSyncAccount {
@@ -49,7 +52,7 @@ class UserDataSyncAccount implements IUserDataSyncAccount {
constructor(readonly authenticationProviderId: string, private readonly session: AuthenticationSession) { }
get sessionId(): string { return this.session.id; }
get accountName(): string { return this.session.account.displayName; }
get accountName(): string { return this.session.account.label; }
get accountId(): string { return this.session.account.id; }
get token(): string { return this.session.accessToken; }
}
@@ -69,13 +72,17 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
readonly onDidChangeAccountStatus = this._onDidChangeAccountStatus.event;
private _all: Map<string, UserDataSyncAccount[]> = new Map<string, UserDataSyncAccount[]>();
get all(): UserDataSyncAccount[] { return flatten(values(this._all)); }
get all(): UserDataSyncAccount[] { return flatten([...this._all.values()]); }
get current(): UserDataSyncAccount | undefined { return this.all.filter(account => this.isCurrentAccount(account))[0]; }
private readonly syncEnablementContext: IContextKey<boolean>;
private readonly syncStatusContext: IContextKey<string>;
private readonly accountStatusContext: IContextKey<string>;
private readonly manualSyncViewEnablementContext: IContextKey<boolean>;
private readonly activityViewsEnablementContext: IContextKey<boolean>;
readonly userDataSyncPreview: UserDataSyncPreview = this._register(new UserDataSyncPreview(this.userDataSyncService));
constructor(
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@@ -93,14 +100,17 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
@INotificationService private readonly notificationService: INotificationService,
@IProgressService private readonly progressService: IProgressService,
@IDialogService private readonly dialogService: IDialogService,
@ICommandService private readonly commandService: ICommandService,
@IContextKeyService contextKeyService: IContextKeyService,
@IViewsService private readonly viewsService: IViewsService,
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
) {
super();
this.authenticationProviders = getUserDataSyncStore(productService, configurationService)?.authenticationProviders || [];
this.syncEnablementContext = CONTEXT_SYNC_ENABLEMENT.bindTo(contextKeyService);
this.syncStatusContext = CONTEXT_SYNC_STATE.bindTo(contextKeyService);
this.accountStatusContext = CONTEXT_ACCOUNT_STATE.bindTo(contextKeyService);
this.activityViewsEnablementContext = CONTEXT_ENABLE_ACTIVITY_VIEWS.bindTo(contextKeyService);
this.manualSyncViewEnablementContext = CONTEXT_ENABLE_MANUAL_SYNC_VIEW.bindTo(contextKeyService);
if (this.authenticationProviders.length) {
@@ -138,7 +148,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
Event.any(
this.authenticationService.onDidRegisterAuthenticationProvider,
this.authenticationService.onDidUnregisterAuthenticationProvider,
), authenticationProviderId => this.isSupportedAuthenticationProviderId(authenticationProviderId)),
), info => this.isSupportedAuthenticationProviderId(info.id)),
Event.filter(this.userDataSyncAccountService.onTokenFailed, isSuccessive => !isSuccessive))
(() => this.update()));
@@ -178,7 +188,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
accounts.set(currentAccount.accountName, currentAccount);
}
return values(accounts);
return [...accounts.values()];
}
private async updateToken(current: UserDataSyncAccount | undefined): Promise<void> {
@@ -223,22 +233,8 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
const preferencesSyncTitle = localize('preferences sync', "Preferences Sync");
const title = `${preferencesSyncTitle} [(${localize('details', "details")})](command:${SHOW_SYNC_LOG_COMMAND_ID})`;
await this.progressService.withProgress({
location: ProgressLocation.Notification,
title,
delay: 500,
}, async (progress) => {
progress.report({ message: localize('turning on', "Turning on...") });
const pullFirst = await this.isSyncingWithAnotherMachine();
const disposable = this.userDataSyncService.onSynchronizeResource(resource =>
progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(resource)) }));
try {
await this.userDataAutoSyncService.turnOn(pullFirst);
} finally {
disposable.dispose();
}
});
await this.syncBeforeTurningOn(title);
await this.userDataAutoSyncService.turnOn();
this.notificationService.info(localize('sync turned on', "{0} is turned on", title));
}
@@ -246,17 +242,69 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
return this.userDataAutoSyncService.turnOff(everywhere);
}
private async isSyncingWithAnotherMachine(): Promise<boolean> {
const isSyncingWithAnotherMachine = await this.userDataSyncService.isFirstTimeSyncingWithAnotherMachine();
if (!isSyncingWithAnotherMachine) {
return false;
private async syncBeforeTurningOn(title: string): Promise<void> {
/* Make sure sync started on clean local state */
await this.userDataSyncService.resetLocal();
const manualSyncTask = await this.userDataSyncService.createManualSyncTask();
try {
let action: FirstTimeSyncAction = 'manual';
let preview: [SyncResource, ISyncResourcePreview][] = [];
await this.progressService.withProgress({
location: ProgressLocation.Notification,
title,
delay: 500,
}, async progress => {
progress.report({ message: localize('turning on', "Turning on...") });
preview = await manualSyncTask.preview();
const hasRemoteData = manualSyncTask.manifest !== null;
const hasLocalData = await this.userDataSyncService.hasLocalData();
const hasChanges = preview.some(([, { resourcePreviews }]) => resourcePreviews.some(r => r.localChange !== Change.None || r.remoteChange !== Change.None));
const isLastSyncFromCurrentMachine = preview.every(([, { isLastSyncFromCurrentMachine }]) => isLastSyncFromCurrentMachine);
action = await this.getFirstTimeSyncAction(hasRemoteData, hasLocalData, hasChanges, isLastSyncFromCurrentMachine);
const progressDisposable = manualSyncTask.onSynchronizeResources(synchronizingResources =>
synchronizingResources.length ? progress.report({ message: localize('syncing resource', "Syncing {0}...", getSyncAreaLabel(synchronizingResources[0][0])) }) : undefined);
try {
switch (action) {
case 'merge': return await manualSyncTask.merge();
case 'pull': return await manualSyncTask.pull();
case 'push': return await manualSyncTask.push();
case 'manual': return;
}
} finally {
progressDisposable.dispose();
}
});
if (action === 'manual') {
await this.syncManually(manualSyncTask, preview);
}
} catch (error) {
await manualSyncTask.stop();
throw error;
} finally {
manualSyncTask.dispose();
}
}
private async getFirstTimeSyncAction(hasRemoteData: boolean, hasLocalData: boolean, hasChanges: boolean, isLastSyncFromCurrentMachine: boolean): Promise<FirstTimeSyncAction> {
if (!hasLocalData /* no data on local */
|| !hasRemoteData /* no data on remote */
|| !hasChanges /* no changes */
|| isLastSyncFromCurrentMachine /* has changes but last sync is from current machine */
) {
return 'merge';
}
const result = await this.dialogService.show(
Severity.Info,
localize('Replace or Merge', "Replace or Merge"),
[
localize('show synced data', "Show Synced Data"),
localize('sync manually', "Sync Manually"),
localize('merge', "Merge"),
localize('replace local', "Replace Local"),
localize('cancel', "Cancel"),
@@ -268,20 +316,72 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
);
switch (result.choice) {
case 0:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'showSyncedData' });
await this.commandService.executeCommand(SHOW_SYNCED_DATA_COMMAND_ID);
throw canceled();
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'manual' });
return 'manual';
case 1:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'merge' });
return false;
return 'merge';
case 2:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'replace-local' });
return true;
case 3:
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' });
throw canceled();
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'pull' });
return 'pull';
}
this.telemetryService.publicLog2<{ action: string }, FirstTimeSyncClassification>('sync/firstTimeSync', { action: 'cancelled' });
throw canceled();
}
private async syncManually(task: IManualSyncTask, preview: [SyncResource, ISyncResourcePreview][]): Promise<void> {
const visibleViewContainer = this.viewsService.getVisibleViewContainer(ViewContainerLocation.Sidebar);
this.userDataSyncPreview.setManualSyncPreview(task, preview);
this.manualSyncViewEnablementContext.set(true);
await this.waitForActiveSyncViews();
await this.viewsService.openView(MANUAL_SYNC_VIEW_ID);
await Event.toPromise(Event.filter(this.userDataSyncPreview.onDidChangeChanges, e => e.length === 0));
if (this.userDataSyncPreview.conflicts.length) {
await Event.toPromise(Event.filter(this.userDataSyncPreview.onDidChangeConflicts, e => e.length === 0));
}
/* Merge to sync globalState changes */
await task.merge();
this.userDataSyncPreview.unsetManualSyncPreview();
this.manualSyncViewEnablementContext.set(false);
if (visibleViewContainer) {
this.viewsService.openViewContainer(visibleViewContainer.id);
} else {
const viewContainer = this.viewDescriptorService.getViewContainerByViewId(MANUAL_SYNC_VIEW_ID);
this.viewsService.closeViewContainer(viewContainer!.id);
}
}
async resetSyncedData(): Promise<void> {
const result = await this.dialogService.confirm({
message: localize('reset', "This will clear your synced data from the cloud and stop sync on all your devices."),
title: localize('reset title', "Reset Synced Data"),
type: 'info',
primaryButton: localize('reset button', "Reset"),
});
if (result.confirmed) {
await this.userDataSyncService.resetRemote();
}
}
async showSyncActivity(): Promise<void> {
this.activityViewsEnablementContext.set(true);
await this.waitForActiveSyncViews();
await this.viewsService.openViewContainer(SYNC_VIEW_CONTAINER_ID);
}
private async waitForActiveSyncViews(): Promise<void> {
const viewContainer = this.viewDescriptorService.getViewContainerById(SYNC_VIEW_CONTAINER_ID);
if (viewContainer) {
const model = this.viewDescriptorService.getViewContainerModel(viewContainer);
if (!model.activeViewDescriptors.length) {
await Event.toPromise(Event.filter(model.onDidChangeActiveViewDescriptors, e => model.activeViewDescriptors.length > 0));
}
}
return false;
}
private isSupportedAuthenticationProviderId(authenticationProviderId: string): boolean {
@@ -305,7 +405,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
if (isAuthenticationProvider(result)) {
const session = await this.authenticationService.login(result.id, result.scopes);
sessionId = session.id;
accountName = session.account.displayName;
accountName = session.account.label;
accountId = session.account.id;
} else {
sessionId = result.sessionId;
@@ -361,7 +461,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
quickPickItems.push({ type: 'separator', label: localize('signed in', "Signed in") });
for (const authenticationProvider of authenticationProviders) {
const accounts = (this._all.get(authenticationProvider.id) || []).sort(({ sessionId }) => sessionId === this.current?.sessionId ? -1 : 1);
const providerName = this.authenticationService.getDisplayName(authenticationProvider.id);
const providerName = this.authenticationService.getLabel(authenticationProvider.id);
for (const account of accounts) {
quickPickItems.push({
label: `${account.accountName} (${providerName})`,
@@ -378,7 +478,7 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
for (const authenticationProvider of this.authenticationProviders) {
const signedInForProvider = this.all.some(account => account.authenticationProviderId === authenticationProvider.id);
if (!signedInForProvider || this.authenticationService.supportsMultipleAccounts(authenticationProvider.id)) {
const providerName = this.authenticationService.getDisplayName(authenticationProvider.id);
const providerName = this.authenticationService.getLabel(authenticationProvider.id);
quickPickItems.push({ label: localize('sign in using account', "Sign in with {0}", providerName), authenticationProvider });
}
}
@@ -458,4 +558,119 @@ export class UserDataSyncWorkbenchService extends Disposable implements IUserDat
}
class UserDataSyncPreview extends Disposable implements IUserDataSyncPreview {
private _changes: ReadonlyArray<IUserDataSyncResourceGroup> = [];
get changes() { return Object.freeze(this._changes); }
private _onDidChangeChanges = this._register(new Emitter<ReadonlyArray<IUserDataSyncResourceGroup>>());
readonly onDidChangeChanges = this._onDidChangeChanges.event;
private _conflicts: ReadonlyArray<IUserDataSyncResourceGroup> = [];
get conflicts() { return Object.freeze(this._conflicts); }
private _onDidChangeConflicts = this._register(new Emitter<ReadonlyArray<IUserDataSyncResourceGroup>>());
readonly onDidChangeConflicts = this._onDidChangeConflicts.event;
private manualSync: { preview: [SyncResource, ISyncResourcePreview][], task: IManualSyncTask, disposables: DisposableStore } | undefined;
constructor(
private readonly userDataSyncService: IUserDataSyncService
) {
super();
this.updateConflicts(userDataSyncService.conflicts);
this._register(userDataSyncService.onDidChangeConflicts(conflicts => this.updateConflicts(conflicts)));
}
setManualSyncPreview(task: IManualSyncTask, preview: [SyncResource, ISyncResourcePreview][]): void {
const disposables = new DisposableStore();
this.manualSync = { task, preview, disposables };
this.updateChanges();
}
unsetManualSyncPreview(): void {
if (this.manualSync) {
this.manualSync.disposables.dispose();
this.manualSync = undefined;
}
this.updateChanges();
}
async accept(syncResource: SyncResource, resource: URI, content: string): Promise<void> {
if (this.manualSync) {
const syncPreview = await this.manualSync.task.accept(resource, content);
this.updatePreview(syncPreview);
} else {
await this.userDataSyncService.acceptPreviewContent(syncResource, resource, content);
}
}
async merge(resource?: URI): Promise<void> {
if (!this.manualSync) {
throw new Error('Can merge only while syncing manually');
}
const syncPreview = await this.manualSync.task.merge(resource);
this.updatePreview(syncPreview);
}
async pull(): Promise<void> {
if (!this.manualSync) {
throw new Error('Can pull only while syncing manually');
}
await this.manualSync.task.pull();
this.updatePreview([]);
}
async push(): Promise<void> {
if (!this.manualSync) {
throw new Error('Can push only while syncing manually');
}
await this.manualSync.task.push();
this.updatePreview([]);
}
private updatePreview(preview: [SyncResource, ISyncResourcePreview][]) {
if (this.manualSync) {
this.manualSync.preview = preview;
this.updateChanges();
}
}
private updateConflicts(conflicts: [SyncResource, IResourcePreview[]][]): void {
const newConflicts = this.toUserDataSyncResourceGroups(conflicts);
if (!equals(newConflicts, this._conflicts, (a, b) => isEqual(a.local, b.local))) {
this._conflicts = newConflicts;
this._onDidChangeConflicts.fire(this.conflicts);
}
this.updateChanges();
}
private updateChanges(): void {
const newChanges = this.toUserDataSyncResourceGroups(
(this.manualSync?.preview || [])
.filter(([syncResource]) => syncResource !== SyncResource.GlobalState) /* Filter Global State Changes */
.map(([syncResource, syncResourcePreview]) =>
([
syncResource,
/* remove merged previews and conflicts and with no changes and conflicts */
syncResourcePreview.resourcePreviews.filter(r =>
!r.merged
&& (r.localChange !== Change.None || r.remoteChange !== Change.None)
&& !this._conflicts.some(c => c.syncResource === syncResource && isEqual(c.local, r.localResource)))
]))
);
if (!equals(newChanges, this._changes, (a, b) => isEqual(a.local, b.local))) {
this._changes = newChanges;
this._onDidChangeChanges.fire(this.changes);
}
}
private toUserDataSyncResourceGroups(syncResourcePreviews: [SyncResource, IResourcePreview[]][]): IUserDataSyncResourceGroup[] {
return flatten(
syncResourcePreviews.map(([syncResource, resourcePreviews]) =>
resourcePreviews.map<IUserDataSyncResourceGroup>(({ localResource, remoteResource, previewResource, localChange, remoteChange }) =>
({ syncResource, local: localResource, remote: remoteResource, preview: previewResource, localChange, remoteChange })))
);
}
}
registerSingleton(IUserDataSyncWorkbenchService, UserDataSyncWorkbenchService);

View File

@@ -4,10 +4,11 @@
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IAuthenticationProvider, SyncStatus, SyncResource } from 'vs/platform/userDataSync/common/userDataSync';
import { IAuthenticationProvider, SyncStatus, SyncResource, Change } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
import { localize } from 'vs/nls';
import { URI } from 'vs/base/common/uri';
export interface IUserDataSyncAccount {
readonly authenticationProviderId: string;
@@ -15,6 +16,28 @@ export interface IUserDataSyncAccount {
readonly accountId: string;
}
export interface IUserDataSyncPreview {
readonly onDidChangeChanges: Event<ReadonlyArray<IUserDataSyncResourceGroup>>;
readonly changes: ReadonlyArray<IUserDataSyncResourceGroup>;
onDidChangeConflicts: Event<ReadonlyArray<IUserDataSyncResourceGroup>>;
readonly conflicts: ReadonlyArray<IUserDataSyncResourceGroup>;
accept(syncResource: SyncResource, resource: URI, content: string): Promise<void>;
merge(resource?: URI): Promise<void>;
pull(): Promise<void>;
push(): Promise<void>;
}
export interface IUserDataSyncResourceGroup {
readonly syncResource: SyncResource;
readonly local: URI;
readonly remote: URI;
readonly preview: URI;
readonly localChange: Change;
readonly remoteChange: Change;
}
export const IUserDataSyncWorkbenchService = createDecorator<IUserDataSyncWorkbenchService>('IUserDataSyncWorkbenchService');
export interface IUserDataSyncWorkbenchService {
_serviceBrand: any;
@@ -26,9 +49,14 @@ export interface IUserDataSyncWorkbenchService {
readonly accountStatus: AccountStatus;
readonly onDidChangeAccountStatus: Event<AccountStatus>;
readonly userDataSyncPreview: IUserDataSyncPreview;
turnOn(): Promise<void>;
turnoff(everyWhere: boolean): Promise<void>;
signIn(): Promise<void>;
resetSyncedData(): Promise<void>;
showSyncActivity(): Promise<void>;
}
export function getSyncAreaLabel(source: SyncResource): string {
@@ -51,10 +79,13 @@ export const enum AccountStatus {
export const CONTEXT_SYNC_STATE = new RawContextKey<string>('syncStatus', SyncStatus.Uninitialized);
export const CONTEXT_SYNC_ENABLEMENT = new RawContextKey<boolean>('syncEnabled', false);
export const CONTEXT_ACCOUNT_STATE = new RawContextKey<string>('userDataSyncAccountStatus', AccountStatus.Uninitialized);
export const CONTEXT_ENABLE_VIEWS = new RawContextKey<boolean>(`showUserDataSyncViews`, false);
export const CONTEXT_ENABLE_ACTIVITY_VIEWS = new RawContextKey<boolean>(`enableSyncActivityViews`, false);
export const CONTEXT_ENABLE_MANUAL_SYNC_VIEW = new RawContextKey<boolean>(`enableManualSyncView`, false);
// Commands
export const ENABLE_SYNC_VIEWS_COMMAND_ID = 'workbench.userDataSync.actions.enableViews';
export const CONFIGURE_SYNC_COMMAND_ID = 'workbench.userDataSync.actions.configure';
export const SHOW_SYNC_LOG_COMMAND_ID = 'workbench.userDataSync.actions.showLog';
export const SHOW_SYNCED_DATA_COMMAND_ID = 'workbench.userDataSync.actions.showSyncedData';
// VIEWS
export const SYNC_VIEW_CONTAINER_ID = 'workbench.view.sync';
export const MANUAL_SYNC_VIEW_ID = 'workbench.views.manualSyncView';

View File

@@ -3,7 +3,7 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError, SyncResourceConflicts, ISyncResourceHandle, ISyncTask } from 'vs/platform/userDataSync/common/userDataSync';
import { SyncStatus, SyncResource, IUserDataSyncService, UserDataSyncError, ISyncResourceHandle, ISyncTask, IManualSyncTask, IUserDataManifest, ISyncResourcePreview, IResourcePreview } from 'vs/platform/userDataSync/common/userDataSync';
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
import { Disposable } from 'vs/base/common/lifecycle';
import { Emitter, Event } from 'vs/base/common/event';
@@ -25,10 +25,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
get onDidChangeLocal(): Event<SyncResource> { return this.channel.listen<SyncResource>('onDidChangeLocal'); }
private _conflicts: SyncResourceConflicts[] = [];
get conflicts(): SyncResourceConflicts[] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<SyncResourceConflicts[]> = this._register(new Emitter<SyncResourceConflicts[]>());
readonly onDidChangeConflicts: Event<SyncResourceConflicts[]> = this._onDidChangeConflicts.event;
private _conflicts: [SyncResource, IResourcePreview[]][] = [];
get conflicts(): [SyncResource, IResourcePreview[]][] { return this._conflicts; }
private _onDidChangeConflicts: Emitter<[SyncResource, IResourcePreview[]][]> = this._register(new Emitter<[SyncResource, IResourcePreview[]][]>());
readonly onDidChangeConflicts: Event<[SyncResource, IResourcePreview[]][]> = this._onDidChangeConflicts.event;
private _lastSyncTime: number | undefined = undefined;
get lastSyncTime(): number | undefined { return this._lastSyncTime; }
@@ -38,10 +38,8 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private _onSyncErrors: Emitter<[SyncResource, UserDataSyncError][]> = this._register(new Emitter<[SyncResource, UserDataSyncError][]>());
readonly onSyncErrors: Event<[SyncResource, UserDataSyncError][]> = this._onSyncErrors.event;
get onSynchronizeResource(): Event<SyncResource> { return this.channel.listen<SyncResource>('onSynchronizeResource'); }
constructor(
@ISharedProcessService sharedProcessService: ISharedProcessService
@ISharedProcessService private readonly sharedProcessService: ISharedProcessService
) {
super();
const userDataSyncChannel = sharedProcessService.getChannel('userDataSync');
@@ -54,7 +52,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return userDataSyncChannel.listen(event, arg);
}
};
this.channel.call<[SyncStatus, SyncResourceConflicts[], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => {
this.channel.call<[SyncStatus, [SyncResource, IResourcePreview[]][], number | undefined]>('_getInitialData').then(([status, conflicts, lastSyncTime]) => {
this.updateStatus(status);
this.updateConflicts(conflicts);
if (lastSyncTime) {
@@ -63,7 +61,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this._register(this.channel.listen<SyncStatus>('onDidChangeStatus')(status => this.updateStatus(status)));
this._register(this.channel.listen<number>('onDidChangeLastSyncTime')(lastSyncTime => this.updateLastSyncTime(lastSyncTime)));
});
this._register(this.channel.listen<SyncResourceConflicts[]>('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts)));
this._register(this.channel.listen<[SyncResource, IResourcePreview[]][]>('onDidChangeConflicts')(conflicts => this.updateConflicts(conflicts)));
this._register(this.channel.listen<[SyncResource, Error][]>('onSyncErrors')(errors => this._onSyncErrors.fire(errors.map(([source, error]) => ([source, UserDataSyncError.toUserDataSyncError(error)])))));
}
@@ -71,16 +69,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('pull');
}
sync(): Promise<void> {
return this.channel.call('sync');
}
createSyncTask(): Promise<ISyncTask> {
throw new Error('not supported');
}
stop(): Promise<void> {
return this.channel.call('stop');
async createManualSyncTask(): Promise<IManualSyncTask> {
const { id, manifest } = await this.channel.call<{ id: string, manifest: IUserDataManifest | null }>('createManualSyncTask');
return new ManualSyncTask(id, manifest, this.sharedProcessService);
}
replace(uri: URI): Promise<void> {
@@ -91,6 +86,10 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('reset');
}
resetRemote(): Promise<void> {
return this.channel.call('resetRemote');
}
resetLocal(): Promise<void> {
return this.channel.call('resetLocal');
}
@@ -99,12 +98,12 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return this.channel.call('hasPreviouslySynced');
}
isFirstTimeSyncingWithAnotherMachine(): Promise<boolean> {
return this.channel.call('isFirstTimeSyncingWithAnotherMachine');
hasLocalData(): Promise<boolean> {
return this.channel.call('hasLocalData');
}
acceptConflict(conflict: URI, content: string): Promise<void> {
return this.channel.call('acceptConflict', [conflict, content]);
acceptPreviewContent(syncResource: SyncResource, resource: URI, content: string): Promise<void> {
return this.channel.call('acceptPreviewContent', [syncResource, resource, content]);
}
resolveContent(resource: URI): Promise<string | null> {
@@ -135,15 +134,20 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this._onDidChangeStatus.fire(status);
}
private async updateConflicts(conflicts: SyncResourceConflicts[]): Promise<void> {
private async updateConflicts(conflicts: [SyncResource, IResourcePreview[]][]): Promise<void> {
// Revive URIs
this._conflicts = conflicts.map(c =>
({
syncResource: c.syncResource,
conflicts: c.conflicts.map(({ local, remote }) =>
({ local: URI.revive(local), remote: URI.revive(remote) }))
}));
this._onDidChangeConflicts.fire(conflicts);
this._conflicts = conflicts.map(([syncResource, conflicts]) =>
([
syncResource,
conflicts.map(r =>
({
...r,
localResource: URI.revive(r.localResource),
remoteResource: URI.revive(r.remoteResource),
previewResource: URI.revive(r.previewResource),
}))
]));
this._onDidChangeConflicts.fire(this._conflicts);
}
private updateLastSyncTime(lastSyncTime: number): void {
@@ -154,4 +158,75 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
}
class ManualSyncTask implements IManualSyncTask {
private readonly channel: IChannel;
get onSynchronizeResources(): Event<[SyncResource, URI[]][]> { return this.channel.listen<[SyncResource, URI[]][]>('onSynchronizeResources'); }
constructor(
readonly id: string,
readonly manifest: IUserDataManifest | null,
sharedProcessService: ISharedProcessService,
) {
const manualSyncTaskChannel = sharedProcessService.getChannel(`manualSyncTask-${id}`);
this.channel = {
call<T>(command: string, arg?: any, cancellationToken?: CancellationToken): Promise<T> {
return manualSyncTaskChannel.call(command, arg, cancellationToken)
.then(null, error => { throw UserDataSyncError.toUserDataSyncError(error); });
},
listen<T>(event: string, arg?: any): Event<T> {
return manualSyncTaskChannel.listen(event, arg);
}
};
}
async preview(): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('preview');
return this.deserializePreviews(previews);
}
async accept(resource: URI, content: string): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('accept', [resource, content]);
return this.deserializePreviews(previews);
}
async merge(resource?: URI): Promise<[SyncResource, ISyncResourcePreview][]> {
const previews = await this.channel.call<[SyncResource, ISyncResourcePreview][]>('merge', [resource]);
return this.deserializePreviews(previews);
}
pull(): Promise<void> {
return this.channel.call('pull');
}
push(): Promise<void> {
return this.channel.call('push');
}
stop(): Promise<void> {
return this.channel.call('stop');
}
dispose(): void {
this.channel.call('dispose');
}
private deserializePreviews(previews: [SyncResource, ISyncResourcePreview][]): [SyncResource, ISyncResourcePreview][] {
return previews.map(([syncResource, preview]) =>
([
syncResource,
{
isLastSyncFromCurrentMachine: preview.isLastSyncFromCurrentMachine,
resourcePreviews: preview.resourcePreviews.map(r => ({
...r,
localResource: URI.revive(r.localResource),
remoteResource: URI.revive(r.remoteResource),
previewResource: URI.revive(r.previewResource),
}))
}
]));
}
}
registerSingleton(IUserDataSyncService, UserDataSyncService);

View File

@@ -206,16 +206,18 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
private onDidRegisterViews(views: { views: IViewDescriptor[], viewContainer: ViewContainer }[]): void {
views.forEach(({ views, viewContainer }) => {
// When views are registered, we need to regroup them based on the cache
const regroupedViews = this.regroupViews(viewContainer.id, views);
this.contextKeyService.bufferChangeEvents(() => {
views.forEach(({ views, viewContainer }) => {
// When views are registered, we need to regroup them based on the cache
const regroupedViews = this.regroupViews(viewContainer.id, views);
// Once they are grouped, try registering them which occurs
// if the container has already been registered within this service
// or we can generate the container from the source view id
this.registerGroupedViews(regroupedViews);
// Once they are grouped, try registering them which occurs
// if the container has already been registered within this service
// or we can generate the container from the source view id
this.registerGroupedViews(regroupedViews);
views.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(!!viewDescriptor.canMoveView));
views.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(!!viewDescriptor.canMoveView));
});
});
}
@@ -227,7 +229,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
// When views are registered, we need to regroup them based on the cache
const regroupedViews = this.regroupViews(viewContainer.id, views);
this.deregisterGroupedViews(regroupedViews);
views.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(false));
this.contextKeyService.bufferChangeEvents(() => {
views.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(false));
});
}
private regroupViews(containerId: string, views: IViewDescriptor[]): Map<string, { cachedContainerInfo?: ICachedViewContainerInfo, views: IViewDescriptor[] }> {
@@ -628,7 +632,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
const viewsToRegister = this.getViewsByContainer(viewContainer).filter(view => this.getDefaultContainerById(view.id) !== viewContainer);
if (viewsToRegister.length) {
this.addViews(viewContainer, viewsToRegister);
viewsToRegister.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(!!viewDescriptor.canMoveView));
this.contextKeyService.bufferChangeEvents(() => {
viewsToRegister.forEach(viewDescriptor => this.getOrCreateMovableViewContextKey(viewDescriptor).set(!!viewDescriptor.canMoveView));
});
}
}
@@ -644,8 +650,10 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
}
private onDidChangeActiveViews({ added, removed }: { added: ReadonlyArray<IViewDescriptor>, removed: ReadonlyArray<IViewDescriptor>; }): void {
added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true));
removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false));
this.contextKeyService.bufferChangeEvents(() => {
added.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(true));
removed.forEach(viewDescriptor => this.getOrCreateActiveViewContextKey(viewDescriptor).set(false));
});
}
private registerResetViewContainerAction(viewContainer: ViewContainer): IDisposable {
@@ -677,9 +685,11 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
private addViews(container: ViewContainer, views: IViewDescriptor[], expandViews?: boolean): void {
// Update in memory cache
views.forEach(view => {
this.cachedViewInfo.set(view.id, { containerId: container.id });
this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainerById(view.id) === container);
this.contextKeyService.bufferChangeEvents(() => {
views.forEach(view => {
this.cachedViewInfo.set(view.id, { containerId: container.id });
this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainerById(view.id) === container);
});
});
this.getViewContainerModel(container).add(views.map(view => { return { viewDescriptor: view, collapsed: expandViews ? false : undefined }; }));
@@ -687,7 +697,9 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
private removeViews(container: ViewContainer, views: IViewDescriptor[]): void {
// Set view default location keys to false
views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false));
this.contextKeyService.bufferChangeEvents(() => {
views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false));
});
// Remove the views
this.getViewContainerModel(container).remove(views);

View File

@@ -14,7 +14,6 @@ import { IInstantiationService } from 'vs/platform/instantiation/common/instanti
import { URI } from 'vs/base/common/uri';
import { firstIndex, move } from 'vs/base/common/arrays';
import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
import { values } from 'vs/base/common/map';
import { isEqual } from 'vs/base/common/resources';
class CounterSet<T> implements IReadableSet<T> {
@@ -181,7 +180,7 @@ class ViewDescriptorsState extends Disposable {
const value = this.storageService.get(this.globalViewsStateStorageId, StorageScope.WORKSPACE, '[]');
const { state: workspaceVisibilityStates } = this.parseStoredGlobalState(value);
if (workspaceVisibilityStates.size > 0) {
for (const { id, isHidden } of values(workspaceVisibilityStates)) {
for (const { id, isHidden } of workspaceVisibilityStates.values()) {
let viewState = viewStates.get(id);
// Not migrated to `viewletStateStorageId`
if (viewState) {
@@ -204,7 +203,7 @@ class ViewDescriptorsState extends Disposable {
if (hasDuplicates) {
this.setStoredGlobalState(state);
}
for (const { id, isHidden, order } of values(state)) {
for (const { id, isHidden, order } of state.values()) {
let viewState = viewStates.get(id);
if (viewState) {
viewState.visibleGlobal = !isHidden;
@@ -229,7 +228,7 @@ class ViewDescriptorsState extends Disposable {
}
private setStoredGlobalState(storedGlobalState: Map<string, IStoredGlobalViewState>): void {
this.globalViewsStatesValue = JSON.stringify(values(storedGlobalState));
this.globalViewsStatesValue = JSON.stringify([...storedGlobalState.values()]);
}
private parseStoredGlobalState(value: string): { state: Map<string, IStoredGlobalViewState>, hasDuplicates: boolean } {

View File

@@ -66,14 +66,14 @@ export class NativeWorkspaceEditingService extends AbstractWorkspaceEditingServi
private registerListeners(): void {
this.lifecycleService.onBeforeShutdown(e => {
const saveOperation = this.saveUntitedBeforeShutdown(e.reason);
const saveOperation = this.saveUntitledBeforeShutdown(e.reason);
if (saveOperation) {
e.veto(saveOperation);
}
});
}
private async saveUntitedBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
private async saveUntitledBeforeShutdown(reason: ShutdownReason): Promise<boolean> {
if (reason !== ShutdownReason.LOAD && reason !== ShutdownReason.CLOSE) {
return false; // only interested when window is closing or loading
}