Merge from vscode 3d67364fbfcf676d93be64f949e9b33e7f1b969e (#5028)

This commit is contained in:
Anthony Dresser
2019-04-14 22:29:14 -07:00
committed by GitHub
parent 6dbf757385
commit 57242a2e13
210 changed files with 4898 additions and 3018 deletions

View File

@@ -83,8 +83,21 @@ export interface IConfigurationRegistry {
}
export const enum ConfigurationScope {
/**
* Application specific configuration, which can be configured only in local user settings.
*/
APPLICATION = 1,
/**
* Machine specific configuration, which can be configured only in local and remote user settings.
*/
MACHINE,
/**
* Window specific configuration, which can be configured in the user or workspace settings.
*/
WINDOW,
/**
* Resource specific configuration, which can be configured in the user, workspace or folder settings.
*/
RESOURCE,
}
@@ -95,6 +108,10 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
tags?: string[];
}
export interface IConfigurationExtensionInfo {
id: string;
}
export interface IConfigurationNode {
id?: string;
order?: number;
@@ -105,7 +122,7 @@ export interface IConfigurationNode {
allOf?: IConfigurationNode[];
overridable?: boolean;
scope?: ConfigurationScope;
contributedByExtension?: boolean;
extensionInfo?: IConfigurationExtensionInfo;
}
export interface IDefaultConfigurationExtension {
@@ -116,6 +133,7 @@ export interface IDefaultConfigurationExtension {
export const allSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const applicationSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const machineSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const windowSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
export const resourceSettings: { properties: {}, patternProperties: {} } = { properties: {}, patternProperties: {} };
@@ -186,6 +204,9 @@ class ConfigurationRegistry implements IConfigurationRegistry {
case ConfigurationScope.APPLICATION:
delete applicationSettings.properties[key];
break;
case ConfigurationScope.MACHINE:
delete machineSettings.properties[key];
break;
case ConfigurationScope.WINDOW:
delete windowSettings.properties[key];
break;
@@ -334,6 +355,9 @@ class ConfigurationRegistry implements IConfigurationRegistry {
case ConfigurationScope.APPLICATION:
applicationSettings.properties[key] = properties[key];
break;
case ConfigurationScope.MACHINE:
machineSettings.properties[key] = properties[key];
break;
case ConfigurationScope.WINDOW:
windowSettings.properties[key] = properties[key];
break;
@@ -371,6 +395,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
delete allSettings.patternProperties[this.overridePropertyPattern];
delete applicationSettings.patternProperties[this.overridePropertyPattern];
delete machineSettings.patternProperties[this.overridePropertyPattern];
delete windowSettings.patternProperties[this.overridePropertyPattern];
delete resourceSettings.patternProperties[this.overridePropertyPattern];
@@ -378,6 +403,7 @@ class ConfigurationRegistry implements IConfigurationRegistry {
allSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
applicationSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
machineSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
windowSettings.patternProperties[this.overridePropertyPattern] = patternProperties;
resourceSettings.patternProperties[this.overridePropertyPattern] = patternProperties;

View File

@@ -18,7 +18,6 @@ export interface ParsedArgs {
waitMarkerFilePath?: string;
diff?: boolean;
add?: boolean;
gitCredential?: string;
goto?: boolean;
'new-window'?: boolean;
'unity-launch'?: boolean; // Always open a new window, except if opening the first window or opening a file or folder as part of the launch.

View File

@@ -197,7 +197,7 @@ function wrapText(text: string, columns: number): string[] {
return lines;
}
export function buildHelpMessage(productName: string, executableName: string, version: string, isOptionSupported = (_: Option) => true): string {
export function buildHelpMessage(productName: string, executableName: string, version: string, isOptionSupported = (_: Option) => true, isPipeSupported = true): string {
const columns = (process.stdout).isTTY && (process.stdout).columns || 80;
let categories = new HelpCategories();

View File

@@ -86,7 +86,7 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
get onDidUninstallExtension(): Event<DidUninstallExtensionEvent> { return this.channel.listen('onDidUninstallExtension'); }
zip(extension: ILocalExtension): Promise<URI> {
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(result)));
return Promise.resolve(this.channel.call('zip', [extension]).then(result => URI.revive(<UriComponents>result)));
}
unzip(zipLocation: URI, type: ExtensionType): Promise<IExtensionIdentifier> {
@@ -122,4 +122,4 @@ export class ExtensionManagementChannelClient implements IExtensionManagementSer
getExtensionsReport(): Promise<IReportedExtension[]> {
return Promise.resolve(this.channel.call('getExtensionsReport'));
}
}
}

View File

@@ -124,11 +124,6 @@ export interface IFileService {
*/
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent>;
/**
* @deprecated use writeFile instead
*/
updateContent(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata>;
/**
* Updates the content replacing its previous value.
*/
@@ -148,18 +143,13 @@ export interface IFileService {
*/
copy(source: URI, target: URI, overwrite?: boolean): Promise<IFileStatWithMetadata>;
/**
* @deprecated use createFile2 instead
*/
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata>;
/**
* Creates a new file with the given path and optional contents. The returned promise
* will have the stat model object as a result.
*
* The optional parameter content can be used as value to fill into the new file.
*/
createFile2(resource: URI, bufferOrReadable?: VSBuffer | VSBufferReadable, options?: ICreateFileOptions): Promise<IFileStatWithMetadata>;
createFile(resource: URI, bufferOrReadable?: VSBuffer | VSBufferReadable, options?: ICreateFileOptions): Promise<IFileStatWithMetadata>;
/**
* Creates a new folder with the given path. The returned promise
@@ -666,6 +656,7 @@ export interface ITextSnapshot {
*/
export function snapshotToString(snapshot: ITextSnapshot): string {
const chunks: string[] = [];
let chunk: string | null;
while (typeof (chunk = snapshot.read()) === 'string') {
chunks.push(chunk);
@@ -674,6 +665,22 @@ export function snapshotToString(snapshot: ITextSnapshot): string {
return chunks.join('');
}
export function stringToSnapshot(value: string): ITextSnapshot {
let done = false;
return {
read(): string | null {
if (!done) {
done = true;
return value;
}
return null;
}
};
}
export class TextSnapshotReadable implements VSBufferReadable {
private preambleHandled: boolean;
@@ -703,6 +710,22 @@ export class TextSnapshotReadable implements VSBufferReadable {
}
}
export function toBufferOrReadable(value: string): VSBuffer;
export function toBufferOrReadable(value: ITextSnapshot): VSBufferReadable;
export function toBufferOrReadable(value: string | ITextSnapshot): VSBuffer | VSBufferReadable;
export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined;
export function toBufferOrReadable(value: string | ITextSnapshot | undefined): VSBuffer | VSBufferReadable | undefined {
if (typeof value === 'undefined') {
return undefined;
}
if (typeof value === 'string') {
return VSBuffer.fromString(value);
}
return new TextSnapshotReadable(value);
}
/**
* Streamable content and meta information of a file.
*/
@@ -1158,8 +1181,4 @@ export interface ILegacyFileService extends IDisposable {
resolveContent(resource: URI, options?: IResolveContentOptions): Promise<IContent>;
resolveStreamContent(resource: URI, options?: IResolveContentOptions): Promise<IStreamContent>;
updateContent(resource: URI, value: string | ITextSnapshot, options?: IWriteTextFileOptions): Promise<IFileStatWithMetadata>;
createFile(resource: URI, content?: string, options?: ICreateFileOptions): Promise<IFileStatWithMetadata>;
}

View File

@@ -15,7 +15,7 @@ import { isWindows, isMacintosh } from 'vs/base/common/platform';
import { IWorkspaceIdentifier, IWorkspacesMainService, ISingleFolderWorkspaceIdentifier, isSingleFolderWorkspaceIdentifier } from 'vs/platform/workspaces/common/workspaces';
import { IHistoryMainService, IRecentlyOpened, isRecentWorkspace, isRecentFolder, IRecent, isRecentFile, IRecentFolder, IRecentWorkspace, IRecentFile } from 'vs/platform/history/common/history';
import { ThrottledDelayer } from 'vs/base/common/async';
import { isEqual as areResourcesEqual, dirname, originalFSPath } from 'vs/base/common/resources';
import { isEqual as areResourcesEqual, dirname, originalFSPath, basename } from 'vs/base/common/resources';
import { URI } from 'vs/base/common/uri';
import { Schemas } from 'vs/base/common/network';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
@@ -29,6 +29,12 @@ export class HistoryMainService implements IHistoryMainService {
private static readonly MAX_MACOS_DOCK_RECENT_FOLDERS = 10;
private static readonly MAX_MACOS_DOCK_RECENT_FILES = 5;
// Exclude some very common files from the dock/taskbar
private static readonly COMMON_FILES_FILTER = [
'COMMIT_EDITMSG',
'MERGE_MSG'
];
private static readonly recentlyOpenedStorageKey = 'openedPathsList';
_serviceBrand: any;
@@ -52,17 +58,29 @@ export class HistoryMainService implements IHistoryMainService {
const files: IRecentFile[] = [];
for (let curr of newlyAdded) {
// Workspace
if (isRecentWorkspace(curr)) {
if (!this.workspacesMainService.isUntitledWorkspace(curr.workspace) && indexOfWorkspace(workspaces, curr.workspace) === -1) {
workspaces.push(curr);
}
} else if (isRecentFolder(curr)) {
}
// Folder
else if (isRecentFolder(curr)) {
if (indexOfFolder(workspaces, curr.folderUri) === -1) {
workspaces.push(curr);
}
} else {
if (indexOfFile(files, curr.fileUri) === -1) {
}
// File
else {
const alreadyExistsInHistory = indexOfFile(files, curr.fileUri) >= 0;
const shouldBeFiltered = curr.fileUri.scheme === Schemas.file && HistoryMainService.COMMON_FILES_FILTER.indexOf(basename(curr.fileUri)) >= 0;
if (!alreadyExistsInHistory && !shouldBeFiltered) {
files.push(curr);
// Add to recent documents (Windows only, macOS later)
if (isWindows && curr.fileUri.scheme === Schemas.file) {
app.addRecentDocument(curr.fileUri.fsPath);
@@ -76,6 +94,7 @@ export class HistoryMainService implements IHistoryMainService {
if (workspaces.length > HistoryMainService.MAX_TOTAL_RECENT_ENTRIES) {
workspaces.length = HistoryMainService.MAX_TOTAL_RECENT_ENTRIES;
}
if (files.length > HistoryMainService.MAX_TOTAL_RECENT_ENTRIES) {
files.length = HistoryMainService.MAX_TOTAL_RECENT_ENTRIES;
}
@@ -143,7 +162,7 @@ export class HistoryMainService implements IHistoryMainService {
// Fill in files
for (let i = 0, entries = 0; i < mru.files.length && entries < HistoryMainService.MAX_MACOS_DOCK_RECENT_FILES; i++) {
const loc = location(mru.files[i]);
if (loc.scheme === Schemas.file) {
if (loc.scheme === Schemas.file && HistoryMainService.COMMON_FILES_FILTER.indexOf(basename(loc)) === -1) {
const filePath = originalFSPath(loc);
if (await exists(filePath)) {
app.addRecentDocument(filePath);
@@ -162,7 +181,6 @@ export class HistoryMainService implements IHistoryMainService {
}
getRecentlyOpened(currentWorkspace?: IWorkspaceIdentifier, currentFolder?: ISingleFolderWorkspaceIdentifier, currentFiles?: IPath[]): IRecentlyOpened {
const workspaces: Array<IRecentFolder | IRecentWorkspace> = [];
const files: IRecentFile[] = [];
@@ -170,6 +188,7 @@ export class HistoryMainService implements IHistoryMainService {
if (currentWorkspace && !this.workspacesMainService.isUntitledWorkspace(currentWorkspace)) {
workspaces.push({ workspace: currentWorkspace });
}
if (currentFolder) {
workspaces.push({ folderUri: currentFolder });
}
@@ -183,12 +202,14 @@ export class HistoryMainService implements IHistoryMainService {
}
}
}
this.addEntriesFromStorage(workspaces, files);
return { workspaces, files };
}
private addEntriesFromStorage(workspaces: Array<IRecentFolder | IRecentWorkspace>, files: IRecentFile[]) {
// Get from storage
let recents = this.getRecentlyOpenedFromStorage();
for (let recent of recents.workspaces) {
@@ -199,6 +220,7 @@ export class HistoryMainService implements IHistoryMainService {
workspaces.push(recent);
}
}
for (let recent of recents.files) {
let index = indexOfFile(files, recent.fileUri);
if (index >= 0) {
@@ -211,11 +233,13 @@ export class HistoryMainService implements IHistoryMainService {
private getRecentlyOpenedFromStorage(): IRecentlyOpened {
const storedRecents = this.stateService.getItem<RecentlyOpenedStorageData>(HistoryMainService.recentlyOpenedStorageKey);
return restoreRecentlyOpened(storedRecents);
}
private saveRecentlyOpened(recent: IRecentlyOpened): void {
const serialized = toStoreData(recent);
this.stateService.setItem(HistoryMainService.recentlyOpenedStorageKey, serialized);
}
@@ -268,16 +292,17 @@ export class HistoryMainService implements IHistoryMainService {
items: arrays.coalesce(this.getRecentlyOpened().workspaces.slice(0, 7 /* limit number of entries here */).map(recent => {
const workspace = isRecentWorkspace(recent) ? recent.workspace : recent.folderUri;
const title = recent.label || getSimpleWorkspaceLabel(workspace, this.environmentService.untitledWorkspacesHome);
let description;
let args;
if (isSingleFolderWorkspaceIdentifier(workspace)) {
const parentFolder = dirname(workspace);
description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(parentFolder, this.environmentService));
description = nls.localize('folderDesc', "{0} {1}", getBaseLabel(workspace), getPathLabel(dirname(workspace), this.environmentService));
args = `--folder-uri "${workspace.toString()}"`;
} else {
description = nls.localize('codeWorkspace', "Code Workspace");
args = `--file-uri "${workspace.configPath.toString()}"`;
}
return <Electron.JumpListItem>{
type: 'task',
title,
@@ -308,9 +333,11 @@ function location(recent: IRecent): URI {
if (isRecentFolder(recent)) {
return recent.folderUri;
}
if (isRecentFile(recent)) {
return recent.fileUri;
}
return recent.workspace.configPath;
}

View File

@@ -55,7 +55,7 @@ export function restoreRecentlyOpened(data: RecentlyOpenedStorageData | undefine
result.workspaces.push({ workspace: { id: workspace['id'], configPath: URI.file(workspace['configPath']) } });
} else if (workspace && typeof workspace['path'] === 'string' && typeof workspace['scheme'] === 'string') {
// added by 1.26-insiders
result.workspaces.push({ folderUri: URI.revive(workspace) });
result.workspaces.push({ folderUri: URI.revive(<UriComponents>workspace) });
}
}
}

View File

@@ -56,6 +56,7 @@ export interface IssueReporterData extends WindowData {
styles: IssueReporterStyles;
enabledExtensions: IssueReporterExtensionData[];
issueType?: IssueType;
extensionId?: string;
}
export interface ISettingSearchResult {

View File

@@ -30,7 +30,8 @@ export const enum ProgressLocation {
Scm = 3,
Extensions = 5,
Window = 10,
Notification = 15
Notification = 15,
Dialog = 20
}
export interface IProgressOptions {

View File

@@ -19,7 +19,6 @@ export interface IRemoteAgentEnvironment {
userHome: URI;
extensions: IExtensionDescription[];
os: OperatingSystem;
syncExtensions: boolean;
}
export interface RemoteAgentConnectionContext {

View File

@@ -22,31 +22,30 @@ export interface IFileChangeDto {
export class RemoteExtensionsFileSystemProvider extends Disposable implements IFileSystemProvider {
private readonly _session: string;
private readonly _channel: IChannel;
private readonly session: string = generateUuid();
private readonly _onDidChange = this._register(new Emitter<IFileChange[]>());
readonly onDidChangeFile: Event<IFileChange[]> = this._onDidChange.event;
public capabilities: FileSystemProviderCapabilities;
private readonly _onDidChangeCapabilities = this._register(new Emitter<void>());
readonly onDidChangeCapabilities: Event<void> = this._onDidChangeCapabilities.event;
constructor(channel: IChannel, environment: Promise<IRemoteAgentEnvironment | null>) {
private _capabilities: FileSystemProviderCapabilities;
get capabilities(): FileSystemProviderCapabilities { return this._capabilities; }
constructor(private readonly channel: IChannel, environment: Promise<IRemoteAgentEnvironment | null>) {
super();
this._session = generateUuid();
this._channel = channel;
this.setCaseSensitive(true);
environment.then(remoteAgentEnvironment => this.setCaseSensitive(!!(remoteAgentEnvironment && remoteAgentEnvironment.os === OperatingSystem.Linux)));
this._channel.listen<IFileChangeDto[]>('filechange', [this._session])((events) => {
this._onDidChange.fire(events.map(RemoteExtensionsFileSystemProvider._createFileChange));
});
this.registerListeners();
}
dispose(): void {
super.dispose();
private registerListeners(): void {
this._register(this.channel.listen<IFileChangeDto[]>('filechange', [this.session])((events) => {
this._onDidChange.fire(events.map(event => ({ resource: URI.revive(event.resource), type: event.type })));
}));
}
setCaseSensitive(isCaseSensitive: boolean) {
@@ -54,58 +53,55 @@ export class RemoteExtensionsFileSystemProvider extends Disposable implements IF
FileSystemProviderCapabilities.FileReadWrite
| FileSystemProviderCapabilities.FileFolderCopy
);
if (isCaseSensitive) {
capabilities |= FileSystemProviderCapabilities.PathCaseSensitive;
}
this.capabilities = capabilities;
this._capabilities = capabilities;
this._onDidChangeCapabilities.fire(undefined);
}
watch(resource: URI, opts: IWatchOptions): IDisposable {
const req = Math.random();
this._channel.call('watch', [this._session, req, resource, opts]);
return toDisposable(() => {
this._channel.call('unwatch', [this._session, req]);
});
}
private static _createFileChange(dto: IFileChangeDto): IFileChange {
return { resource: URI.revive(dto.resource), type: dto.type };
}
// --- forwarding calls
stat(resource: URI): Promise<IStat> {
return this._channel.call('stat', [resource]);
return this.channel.call('stat', [resource]);
}
async readFile(resource: URI): Promise<Uint8Array> {
const buff = <VSBuffer>await this._channel.call('readFile', [resource]);
const buff = <VSBuffer>await this.channel.call('readFile', [resource]);
return buff.buffer;
}
writeFile(resource: URI, content: Uint8Array, opts: FileWriteOptions): Promise<void> {
const contents = VSBuffer.wrap(content);
return this._channel.call('writeFile', [resource, contents, opts]);
return this.channel.call('writeFile', [resource, VSBuffer.wrap(content), opts]);
}
delete(resource: URI, opts: FileDeleteOptions): Promise<void> {
return this._channel.call('delete', [resource, opts]);
return this.channel.call('delete', [resource, opts]);
}
mkdir(resource: URI): Promise<void> {
return this._channel.call('mkdir', [resource]);
return this.channel.call('mkdir', [resource]);
}
readdir(resource: URI): Promise<[string, FileType][]> {
return this._channel.call('readdir', [resource]);
return this.channel.call('readdir', [resource]);
}
rename(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
return this._channel.call('rename', [resource, target, opts]);
return this.channel.call('rename', [resource, target, opts]);
}
copy(resource: URI, target: URI, opts: FileOverwriteOptions): Promise<void> {
return this._channel.call('copy', [resource, target, opts]);
return this.channel.call('copy', [resource, target, opts]);
}
watch(resource: URI, opts: IWatchOptions): IDisposable {
const req = Math.random();
this.channel.call('watch', [this.session, req, resource, opts]);
return toDisposable(() => this.channel.call('unwatch', [this.session, req]));
}
}

View File

@@ -3,69 +3,12 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { binarySearch } from 'vs/base/common/arrays';
import { toDisposable } from 'vs/base/common/lifecycle';
import { globals } from 'vs/base/common/platform';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { IDisposable, toDisposable, dispose } from 'vs/base/common/lifecycle';
import * as Errors from 'vs/base/common/errors';
import { safeStringify } from 'vs/base/common/objects';
import BaseErrorTelemetry, { ErrorEvent } from '../common/errorTelemetry';
/* __GDPR__FRAGMENT__
"ErrorEvent" : {
"stack": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"message" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"filename" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"callstack": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"msg" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"file" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"line": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"column": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"uncaught_error_name": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"uncaught_error_msg": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"count": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
interface ErrorEvent {
callstack: string;
msg?: string;
file?: string;
line?: number;
column?: number;
uncaught_error_name?: string;
uncaught_error_msg?: string;
count?: number;
}
namespace ErrorEvent {
export function compare(a: ErrorEvent, b: ErrorEvent) {
if (a.callstack < b.callstack) {
return -1;
} else if (a.callstack > b.callstack) {
return 1;
}
return 0;
}
}
export default class ErrorTelemetry {
public static ERROR_FLUSH_TIMEOUT: number = 5 * 1000;
private _telemetryService: ITelemetryService;
private _flushDelay: number;
private _flushHandle: any = -1;
private _buffer: ErrorEvent[] = [];
private _disposables: IDisposable[] = [];
constructor(telemetryService: ITelemetryService, flushDelay = ErrorTelemetry.ERROR_FLUSH_TIMEOUT) {
this._telemetryService = telemetryService;
this._flushDelay = flushDelay;
// (1) check for unexpected but handled errors
const unbind = Errors.errorHandler.addListener((err) => this._onErrorEvent(err));
this._disposables.push(toDisposable(unbind));
// (2) check for uncaught global errors
export default class ErrorTelemetry extends BaseErrorTelemetry {
protected installErrorListeners(): void {
let oldOnError: Function;
let that = this;
if (typeof globals.onerror === 'function') {
@@ -84,37 +27,7 @@ export default class ErrorTelemetry {
}));
}
dispose() {
clearTimeout(this._flushHandle);
this._flushBuffer();
this._disposables = dispose(this._disposables);
}
private _onErrorEvent(err: any): void {
if (!err) {
return;
}
// unwrap nested errors from loader
if (err.detail && err.detail.stack) {
err = err.detail;
}
// work around behavior in workerServer.ts that breaks up Error.stack
let callstack = Array.isArray(err.stack) ? err.stack.join('\n') : err.stack;
let msg = err.message ? err.message : safeStringify(err);
// errors without a stack are not useful telemetry
if (!callstack) {
return;
}
this._enqueue({ msg, callstack });
}
private _onUncaughtError(msg: string, file: string, line: number, column?: number, err?: any): void {
let data: ErrorEvent = {
callstack: msg,
msg,
@@ -138,38 +51,4 @@ export default class ErrorTelemetry {
this._enqueue(data);
}
private _enqueue(e: ErrorEvent): void {
const idx = binarySearch(this._buffer, e, ErrorEvent.compare);
if (idx < 0) {
e.count = 1;
this._buffer.splice(~idx, 0, e);
} else {
if (!this._buffer[idx].count) {
this._buffer[idx].count = 0;
}
this._buffer[idx].count! += 1;
}
if (this._flushHandle === -1) {
this._flushHandle = setTimeout(() => {
this._flushBuffer();
this._flushHandle = -1;
}, this._flushDelay);
}
}
private _flushBuffer(): void {
for (let error of this._buffer) {
/* __GDPR__
"UnhandledError" : {
"${include}": [ "${ErrorEvent}" ]
}
*/
// {{SQL CARBON EDIT}}
//this._telemetryService.publicLog('UnhandledError', error, true);
}
this._buffer.length = 0;
}
}

View File

@@ -0,0 +1,137 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { binarySearch } from 'vs/base/common/arrays';
import * as Errors from 'vs/base/common/errors';
import { dispose, IDisposable, toDisposable } from 'vs/base/common/lifecycle';
import { safeStringify } from 'vs/base/common/objects';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
/* __GDPR__FRAGMENT__
"ErrorEvent" : {
"stack": { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"message" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"filename" : { "classification": "CustomerContent", "purpose": "PerformanceAndHealth" },
"callstack": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"msg" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"file" : { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"line": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"column": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true },
"uncaught_error_name": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"uncaught_error_msg": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth" },
"count": { "classification": "CallstackOrException", "purpose": "PerformanceAndHealth", "isMeasurement": true }
}
*/
export interface ErrorEvent {
callstack: string;
msg?: string;
file?: string;
line?: number;
column?: number;
uncaught_error_name?: string;
uncaught_error_msg?: string;
count?: number;
}
export namespace ErrorEvent {
export function compare(a: ErrorEvent, b: ErrorEvent) {
if (a.callstack < b.callstack) {
return -1;
} else if (a.callstack > b.callstack) {
return 1;
}
return 0;
}
}
export default abstract class BaseErrorTelemetry {
public static ERROR_FLUSH_TIMEOUT: number = 5 * 1000;
private _telemetryService: ITelemetryService;
private _flushDelay: number;
private _flushHandle: any = -1;
private _buffer: ErrorEvent[] = [];
protected _disposables: IDisposable[] = [];
constructor(telemetryService: ITelemetryService, flushDelay = BaseErrorTelemetry.ERROR_FLUSH_TIMEOUT) {
this._telemetryService = telemetryService;
this._flushDelay = flushDelay;
// (1) check for unexpected but handled errors
const unbind = Errors.errorHandler.addListener((err) => this._onErrorEvent(err));
this._disposables.push(toDisposable(unbind));
// (2) install implementation-specific error listeners
this.installErrorListeners();
}
dispose() {
clearTimeout(this._flushHandle);
this._flushBuffer();
this._disposables = dispose(this._disposables);
}
protected installErrorListeners(): void {
// to override
}
private _onErrorEvent(err: any): void {
if (!err) {
return;
}
// unwrap nested errors from loader
if (err.detail && err.detail.stack) {
err = err.detail;
}
// work around behavior in workerServer.ts that breaks up Error.stack
let callstack = Array.isArray(err.stack) ? err.stack.join('\n') : err.stack;
let msg = err.message ? err.message : safeStringify(err);
// errors without a stack are not useful telemetry
if (!callstack) {
return;
}
this._enqueue({ msg, callstack });
}
protected _enqueue(e: ErrorEvent): void {
const idx = binarySearch(this._buffer, e, ErrorEvent.compare);
if (idx < 0) {
e.count = 1;
this._buffer.splice(~idx, 0, e);
} else {
if (!this._buffer[idx].count) {
this._buffer[idx].count = 0;
}
this._buffer[idx].count! += 1;
}
if (this._flushHandle === -1) {
this._flushHandle = setTimeout(() => {
this._flushBuffer();
this._flushHandle = -1;
}, this._flushDelay);
}
}
private _flushBuffer(): void {
for (let error of this._buffer) {
/* __GDPR__
"UnhandledError" : {
"${include}": [ "${ErrorEvent}" ]
}
*/
// {{SQL CARBON EDIT}}
// this._telemetryService.publicLog('UnhandledError', error, true);
}
this._buffer.length = 0;
}
}

View File

@@ -29,6 +29,8 @@ export interface ITelemetryService {
*/
publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<void>;
setEnabled(value: boolean): void;
getTelemetryInfo(): Promise<ITelemetryInfo>;
isOptedIn: boolean;

View File

@@ -31,6 +31,7 @@ export class TelemetryService implements ITelemetryService {
private _commonProperties: Promise<{ [name: string]: any; }>;
private _piiPaths: string[];
private _userOptIn: boolean;
private _enabled: boolean;
private _disposables: IDisposable[] = [];
private _cleanupPatterns: RegExp[] = [];
@@ -43,6 +44,7 @@ export class TelemetryService implements ITelemetryService {
this._commonProperties = config.commonProperties || Promise.resolve({});
this._piiPaths = config.piiPaths || [];
this._userOptIn = true;
this._enabled = true;
// static cleanup pattern for: `file:///DANGEROUS/PATH/resources/app/Useful/Information`
this._cleanupPatterns = [/file:\/\/\/.*?\/resources\/app\//gi];
@@ -74,13 +76,17 @@ export class TelemetryService implements ITelemetryService {
}
}
setEnabled(value: boolean): void {
this._enabled = value;
}
private _updateUserOptIn(): void {
const config = this._configurationService.getValue<any>(TELEMETRY_SECTION_ID);
this._userOptIn = config ? config.enableTelemetry : this._userOptIn;
}
get isOptedIn(): boolean {
return this._userOptIn;
return this._userOptIn && this._enabled;
}
getTelemetryInfo(): Promise<ITelemetryInfo> {
@@ -100,7 +106,7 @@ export class TelemetryService implements ITelemetryService {
publicLog(eventName: string, data?: ITelemetryData, anonymizeFilePaths?: boolean): Promise<any> {
// don't send events when the user is optout
if (!this._userOptIn) {
if (!this.isOptedIn) {
return Promise.resolve(undefined);
}

View File

@@ -14,6 +14,7 @@ export const NullTelemetryService = new class implements ITelemetryService {
publicLog(eventName: string, data?: ITelemetryData) {
return Promise.resolve(undefined);
}
setEnabled() { }
isOptedIn: true;
getTelemetryInfo(): Promise<ITelemetryInfo> {
return Promise.resolve({

View File

@@ -0,0 +1,44 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { onUnexpectedError } from 'vs/base/common/errors';
import BaseErrorTelemetry from '../common/errorTelemetry';
export default class ErrorTelemetry extends BaseErrorTelemetry {
protected installErrorListeners(): void {
// Print a console message when rejection isn't handled within N seconds. For details:
// see https://nodejs.org/api/process.html#process_event_unhandledrejection
// and https://nodejs.org/api/process.html#process_event_rejectionhandled
const unhandledPromises: Promise<any>[] = [];
process.on('unhandledRejection', (reason: any, promise: Promise<any>) => {
unhandledPromises.push(promise);
setTimeout(() => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
promise.catch(e => {
unhandledPromises.splice(idx, 1);
console.warn(`rejected promise not handled within 1 second: ${e}`);
if (e.stack) {
console.warn(`stack trace: ${e.stack}`);
}
onUnexpectedError(reason);
});
}
}, 1000);
});
process.on('rejectionHandled', (promise: Promise<any>) => {
const idx = unhandledPromises.indexOf(promise);
if (idx >= 0) {
unhandledPromises.splice(idx, 1);
}
});
// Print a console message when an exception isn't handled.
process.on('uncaughtException', (err: Error) => {
onUnexpectedError(err);
});
}
}

View File

@@ -16,6 +16,8 @@ import * as json from 'vs/base/common/json';
import { Schemas } from 'vs/base/common/network';
import { normalizeDriveLetter } from 'vs/base/common/labels';
import { toSlashes } from 'vs/base/common/extpath';
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { getRemoteAuthority } from 'vs/platform/remote/common/remoteHosts';
export const IWorkspacesMainService = createDecorator<IWorkspacesMainService>('workspacesMainService');
export const IWorkspacesService = createDecorator<IWorkspacesService>('workspacesService');
@@ -75,6 +77,7 @@ export interface IResolvedWorkspace extends IWorkspaceIdentifier {
export interface IStoredWorkspace {
folders: IStoredWorkspaceFolder[];
remoteAuthority?: string;
}
export interface IWorkspaceSavedEvent {
@@ -232,12 +235,15 @@ export function rewriteWorkspaceFileForNewLocation(rawWorkspaceContents: string,
// Preserve as much of the existing workspace as possible by using jsonEdit
// and only changing the folders portion.
let newRawWorkspaceContents = rawWorkspaceContents;
const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], rewrittenFolders, { insertSpaces: false, tabSize: 4, eol: (isLinux || isMacintosh) ? '\n' : '\r\n' });
edits.forEach(edit => {
newRawWorkspaceContents = jsonEdit.applyEdit(rawWorkspaceContents, edit);
});
return newRawWorkspaceContents;
const formattingOptions: FormattingOptions = { insertSpaces: false, tabSize: 4, eol: (isLinux || isMacintosh) ? '\n' : '\r\n' };
const edits = jsonEdit.setProperty(rawWorkspaceContents, ['folders'], rewrittenFolders, formattingOptions);
let newContent = jsonEdit.applyEdits(rawWorkspaceContents, edits);
if (storedWorkspace.remoteAuthority === getRemoteAuthority(targetConfigPathURI)) {
// unsaved remote workspaces have the remoteAuthority set. Remove it when no longer nexessary.
newContent = jsonEdit.applyEdits(newContent, jsonEdit.removeProperty(newContent, ['remoteAuthority'], formattingOptions));
}
return newContent;
}
function doParseStoredWorkspace(path: URI, contents: string): IStoredWorkspace {