mirror of
https://github.com/ckaczor/azuredatastudio.git
synced 2026-02-16 18:46:40 -05:00
Merge from vscode 3d67364fbfcf676d93be64f949e9b33e7f1b969e (#5028)
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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) });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +56,7 @@ export interface IssueReporterData extends WindowData {
|
||||
styles: IssueReporterStyles;
|
||||
enabledExtensions: IssueReporterExtensionData[];
|
||||
issueType?: IssueType;
|
||||
extensionId?: string;
|
||||
}
|
||||
|
||||
export interface ISettingSearchResult {
|
||||
|
||||
@@ -30,7 +30,8 @@ export const enum ProgressLocation {
|
||||
Scm = 3,
|
||||
Extensions = 5,
|
||||
Window = 10,
|
||||
Notification = 15
|
||||
Notification = 15,
|
||||
Dialog = 20
|
||||
}
|
||||
|
||||
export interface IProgressOptions {
|
||||
|
||||
@@ -19,7 +19,6 @@ export interface IRemoteAgentEnvironment {
|
||||
userHome: URI;
|
||||
extensions: IExtensionDescription[];
|
||||
os: OperatingSystem;
|
||||
syncExtensions: boolean;
|
||||
}
|
||||
|
||||
export interface RemoteAgentConnectionContext {
|
||||
|
||||
@@ -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]));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
137
src/vs/platform/telemetry/common/errorTelemetry.ts
Normal file
137
src/vs/platform/telemetry/common/errorTelemetry.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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({
|
||||
|
||||
44
src/vs/platform/telemetry/node/errorTelemetry.ts
Normal file
44
src/vs/platform/telemetry/node/errorTelemetry.ts
Normal 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);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user