Merge remote-tracking branch 'microsoft/master' into movableViews

This commit is contained in:
SteVen Batten
2020-01-21 10:49:36 -08:00
293 changed files with 7268 additions and 4234 deletions

View File

@@ -83,7 +83,7 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape {
private deriveConfigurationTarget(key: string, overrides: IConfigurationOverrides): ConfigurationTarget {
if (overrides.resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
if (configurationProperties[key] && (configurationProperties[key].scope === ConfigurationScope.RESOURCE || configurationProperties[key].scope === ConfigurationScope.RESOURCE_LANGUAGE)) {
if (configurationProperties[key] && (configurationProperties[key].scope === ConfigurationScope.RESOURCE || configurationProperties[key].scope === ConfigurationScope.LANGUAGE_OVERRIDABLE)) {
return ConfigurationTarget.WORKSPACE_FOLDER;
}
}

View File

@@ -216,7 +216,7 @@ export class MainThreadTextEditors implements MainThreadTextEditorsShape {
}
$tryApplyWorkspaceEdit(dto: IWorkspaceEditDto): Promise<boolean> {
const { edits } = reviveWorkspaceEditDto(dto);
const { edits } = reviveWorkspaceEditDto(dto)!;
return this._bulkEditService.apply({ edits }).then(() => true, _err => false);
}

View File

@@ -316,7 +316,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
$registerRenameSupport(handle: number, selector: IDocumentFilterDto[], supportResolveLocation: boolean): void {
this._registrations.set(handle, modes.RenameProviderRegistry.register(selector, <modes.RenameProvider>{
provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken): Promise<modes.WorkspaceEdit> => {
provideRenameEdits: (model: ITextModel, position: EditorPosition, newName: string, token: CancellationToken) => {
return this._proxy.$provideRenameEdits(handle, model.uri, position, newName, token).then(reviveWorkspaceEditDto);
},
resolveRenameLocation: supportResolveLocation
@@ -340,7 +340,7 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
private static _inflateSuggestDto(defaultRange: IRange | { insert: IRange, replace: IRange }, data: ISuggestDataDto): modes.CompletionItem {
return {
label: data[ISuggestDataDtoField.label],
label: data[ISuggestDataDtoField.label2] || data[ISuggestDataDtoField.label],
kind: data[ISuggestDataDtoField.kind],
tags: data[ISuggestDataDtoField.kindModifier],
detail: data[ISuggestDataDtoField.detail],
@@ -371,7 +371,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
return {
suggestions: result.b.map(d => MainThreadLanguageFeatures._inflateSuggestDto(result.a, d)),
incomplete: result.c,
isDetailsResolved: result.d,
dispose: () => typeof result.x === 'number' && this._proxy.$releaseCompletionItems(handle, result.x)
};
});

View File

@@ -32,23 +32,16 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF
import { ISaveParticipant, IResolvedTextFileEditorModel } from 'vs/workbench/services/textfile/common/textfiles';
import { SaveReason } from 'vs/workbench/common/editor';
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../common/extHost.protocol';
import { IStatusbarService, StatusbarAlignment } from 'vs/workbench/services/statusbar/common/statusbar';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { SettingsEditor2 } from 'vs/workbench/contrib/preferences/browser/settingsEditor2';
import { IPreferencesService } from 'vs/workbench/services/preferences/common/preferences';
import { ILabelService } from 'vs/platform/label/common/label';
import { canceled, isPromiseCanceledError } from 'vs/base/common/errors';
import { canceled } from 'vs/base/common/errors';
export interface ICodeActionsOnSaveOptions {
[kind: string]: boolean;
}
class SaveParticipantError extends Error {
constructor(message: string, readonly setting?: string) {
super(message);
}
}
export interface ISaveParticipantParticipant {
participate(model: IResolvedTextFileEditorModel, env: { reason: SaveReason }, progress: IProgress<IProgressStep>, token: CancellationToken): Promise<void>;
}
@@ -338,7 +331,7 @@ class ExtHostSaveParticipant implements ISaveParticipantParticipant {
token.onCancellationRequested(() => reject(canceled()));
setTimeout(
() => reject(new SaveParticipantError(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))),
() => reject(new Error(localize('timeout.onWillSave', "Aborted onWillSaveTextDocument-event after 1750ms"))),
1750
);
this._proxy.$participateInSave(editorModel.resource, env.reason).then(values => {
@@ -361,7 +354,6 @@ export class SaveParticipant implements ISaveParticipant {
extHostContext: IExtHostContext,
@IInstantiationService instantiationService: IInstantiationService,
@IProgressService private readonly _progressService: IProgressService,
@IStatusbarService private readonly _statusbarService: IStatusbarService,
@ILogService private readonly _logService: ILogService,
@ILabelService private readonly _labelService: ILabelService,
) {
@@ -393,54 +385,23 @@ export class SaveParticipant implements ISaveParticipant {
delay: model.isDirty() ? 3000 : 5000
}, async progress => {
let firstError: SaveParticipantError | undefined;
for (let p of this._saveParticipants.getValue()) {
if (cts.token.isCancellationRequested) {
break;
}
try {
await p.participate(model, env, progress, cts.token);
} catch (err) {
if (!isPromiseCanceledError(err)) {
this._logService.warn(err);
firstError = !firstError && err instanceof SaveParticipantError ? err : firstError;
}
this._logService.warn(err);
}
}
if (firstError) {
this._showParticipantError(firstError);
}
}, () => {
// user cancel
cts.dispose(true);
});
}
private _showParticipantError(err: SaveParticipantError): void {
let entry: any = {
text: localize('title', "$(error) Save Participants Failed: {0}", err.message)
};
if (err.setting) {
entry.command = '_showSettings';
entry.arguments = [err.setting];
}
const handle = this._statusbarService.addEntry(
entry,
'saveParticipants.error',
localize('status.message', "Save Participants Errors"),
StatusbarAlignment.LEFT,
-Number.MAX_VALUE /* far right on left hand side */
);
setTimeout(() => handle.dispose(), 5000);
}
}
CommandsRegistry.registerCommand('_showSettings', (accessor, ...args: any[]) => {

View File

@@ -8,9 +8,11 @@ import { TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
import { ITunnelProvider, ITunnelService, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { Disposable } from 'vs/base/common/lifecycle';
import type { TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
export class MainThreadTunnelService extends Disposable implements MainThreadTunnelServiceShape {
private readonly _proxy: ExtHostTunnelServiceShape;
constructor(
@@ -18,7 +20,10 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService,
@ITunnelService private readonly tunnelService: ITunnelService
) {
super();
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
this._register(tunnelService.onTunnelOpened(() => this._proxy.$onDidTunnelsChange()));
this._register(tunnelService.onTunnelClosed(() => this._proxy.$onDidTunnelsChange()));
}
async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> {
@@ -33,6 +38,15 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
return this.remoteExplorerService.close(remote);
}
async $getTunnels(): Promise<TunnelDescription[]> {
return (await this.tunnelService.tunnels).map(tunnel => {
return {
remoteAddress: { port: tunnel.tunnelRemotePort, host: tunnel.tunnelRemoteHost },
localAddress: tunnel.localAddress
};
});
}
async $registerCandidateFinder(): Promise<void> {
this.remoteExplorerService.registerCandidateFinder(() => this._proxy.$findCandidatePorts());
}
@@ -59,7 +73,23 @@ export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
this.tunnelService.setTunnelProvider(tunnelProvider);
}
async $setCandidateFilter(): Promise<void> {
this._register(this.remoteExplorerService.setCandidateFilter(async (candidates: { host: string, port: number, detail: string }[]): Promise<{ host: string, port: number, detail: string }[]> => {
const filters: boolean[] = await this._proxy.$filterCandidates(candidates);
const filteredCandidates: { host: string, port: number, detail: string }[] = [];
if (filters.length !== candidates.length) {
return candidates;
}
for (let i = 0; i < candidates.length; i++) {
if (filters[i]) {
filteredCandidates.push(candidates[i]);
}
}
return filteredCandidates;
}));
}
dispose(): void {
//
}
}

View File

@@ -306,6 +306,8 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
provider.dispose();
this._editorProviders.delete(viewType);
this._customEditorService.models.disposeAllModelsForView(viewType);
}
private async retainCustomEditorModel(webviewInput: WebviewInput, resource: URI, viewType: string, capabilities: readonly extHostProtocol.WebviewEditorCapabilities[]) {
@@ -323,21 +325,23 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
const capabilitiesSet = new Set(capabilities);
const isEditable = capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.Editable);
if (isEditable) {
model.onUndo(edits => {
this._proxy.$undoEdits(resource, viewType, edits.map(x => x.data));
model.onUndo(e => {
this._proxy.$undoEdits(resource, viewType, e.edits);
});
model.onApplyEdit(edits => {
const editsToApply = edits.filter(x => x.source !== model).map(x => x.data);
if (editsToApply.length) {
this._proxy.$applyEdits(resource, viewType, editsToApply);
model.onDisposeEdits(e => {
this._proxy.$disposeEdits(e.edits);
});
model.onApplyEdit(e => {
if (e.trigger !== model) {
this._proxy.$applyEdits(resource, viewType, e.edits);
}
});
model.onWillSave(e => {
e.waitUntil(this._proxy.$onSave(resource.toJSON(), viewType));
});
}
// Save as should always be implemented even if the model is readonly
@@ -349,6 +353,11 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
e.waitUntil(this._fileService.copy(e.resource, e.targetResource, false /* overwrite */));
}
});
if (capabilitiesSet.has(extHostProtocol.WebviewEditorCapabilities.SupportsHotExit)) {
// TODO: Hook up hot exit / backup logic
}
return model;
}
@@ -365,13 +374,13 @@ export class MainThreadWebviews extends Disposable implements extHostProtocol.Ma
}
}
public $onEdit(resource: UriComponents, viewType: string, editData: any): void {
public $onEdit(resource: UriComponents, viewType: string, editId: number): void {
const model = this._customEditorService.models.get(URI.revive(resource), viewType);
if (!model) {
throw new Error('Could not find model for webview editor');
}
model.pushEdit({ source: model, data: editData });
model.pushEdit(editId, model);
}
private hookupWebviewEventDelegate(handle: extHostProtocol.WebviewPanelHandle, input: WebviewInput) {

View File

@@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
export class Cache<T> {
private static readonly enableDebugLogging = false;
private readonly _data = new Map<number, readonly T[]>();
private _idPool = 1;
constructor(
private readonly id: string
) { }
add(item: readonly T[]): number {
const id = this._idPool++;
this._data.set(id, item);
this.logDebugInfo();
return id;
}
get(pid: number, id: number): T | undefined {
return this._data.has(pid) ? this._data.get(pid)![id] : undefined;
}
delete(id: number) {
this._data.delete(id);
this.logDebugInfo();
}
private logDebugInfo() {
if (!Cache.enableDebugLogging) {
return;
}
console.log(`${this.id} cache size — ${this._data.size}`);
}
}

View File

@@ -39,13 +39,14 @@ const configurationEntrySchema: IJSONSchema = {
},
scope: {
type: 'string',
enum: ['application', 'machine', 'window', 'resource', 'machine-overridable'],
enum: ['application', 'machine', 'window', 'resource', 'language-overridable', 'machine-overridable'],
default: 'window',
enumDescriptions: [
nls.localize('scope.application.description', "Configuration that can be configured only in the user settings."),
nls.localize('scope.machine.description', "Configuration that can be configured only in the user settings when the extension is running locally, or only in the remote settings when the extension is running remotely."),
nls.localize('scope.window.description', "Configuration that can be configured in the user, remote or workspace settings."),
nls.localize('scope.resource.description', "Configuration that can be configured in the user, remote, workspace or folder settings."),
nls.localize('scope.language-overridable.description', "Resource configuration that can be configured in language specific settings."),
nls.localize('scope.machine-overridable.description', "Machine configuration that can be configured also in workspace or folder settings.")
],
description: nls.localize('scope.description', "Scope in which the configuration is applicable. Available scopes are `application`, `machine`, `window`, `resource`, and `machine-overridable`.")
@@ -218,8 +219,8 @@ function validateProperties(configuration: IConfigurationNode, extension: IExten
propertyConfiguration.scope = ConfigurationScope.RESOURCE;
} else if (propertyConfiguration.scope.toString() === 'machine-overridable') {
propertyConfiguration.scope = ConfigurationScope.MACHINE_OVERRIDABLE;
} else if (propertyConfiguration.scope.toString() === 'resource-language') {
propertyConfiguration.scope = ConfigurationScope.RESOURCE_LANGUAGE;
} else if (propertyConfiguration.scope.toString() === 'language-overridable') {
propertyConfiguration.scope = ConfigurationScope.LANGUAGE_OVERRIDABLE;
} else {
propertyConfiguration.scope = ConfigurationScope.WINDOW;
}

View File

@@ -762,6 +762,14 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
openTunnel: (forward: vscode.TunnelOptions) => {
checkProposedApiEnabled(extension);
return extHostTunnelService.openTunnel(forward);
},
get tunnels() {
checkProposedApiEnabled(extension);
return extHostTunnelService.getTunnels();
},
onDidTunnelsChange: (listener, thisArg?, disposables?) => {
checkProposedApiEnabled(extension);
return extHostTunnelService.onDidTunnelsChange(listener, thisArg, disposables);
}
};

View File

@@ -31,7 +31,7 @@ import { LogLevel } from 'vs/platform/log/common/log';
import { IMarkerData } from 'vs/platform/markers/common/markers';
import { IProgressOptions, IProgressStep } from 'vs/platform/progress/common/progress';
import * as quickInput from 'vs/platform/quickinput/common/quickInput';
import { RemoteAuthorityResolverErrorCode, ResolverResult } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { RemoteAuthorityResolverErrorCode, ResolverResult, TunnelDescription } from 'vs/platform/remote/common/remoteAuthorityResolver';
import * as statusbar from 'vs/workbench/services/statusbar/common/statusbar';
import { ClassifiedEvent, GDPRClassification, StrictPropertyCheck } from 'vs/platform/telemetry/common/gdprTypings';
import { ITelemetryInfo } from 'vs/platform/telemetry/common/telemetry';
@@ -563,6 +563,7 @@ export interface WebviewExtensionDescription {
export enum WebviewEditorCapabilities {
Editable,
SupportsHotExit,
}
export interface MainThreadWebviewsShape extends IDisposable {
@@ -583,7 +584,7 @@ export interface MainThreadWebviewsShape extends IDisposable {
$registerEditorProvider(extension: WebviewExtensionDescription, viewType: string, options: modes.IWebviewPanelOptions, capabilities: readonly WebviewEditorCapabilities[]): void;
$unregisterEditorProvider(viewType: string): void;
$onEdit(resource: UriComponents, viewType: string, editJson: any): void;
$onEdit(resource: UriComponents, viewType: string, editId: number): void;
}
export interface WebviewPanelViewStateData {
@@ -603,8 +604,9 @@ export interface ExtHostWebviewsShape {
$deserializeWebviewPanel(newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, state: any, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$resolveWebviewEditor(resource: UriComponents, newWebviewHandle: WebviewPanelHandle, viewType: string, title: string, position: EditorViewColumn, options: modes.IWebviewOptions & modes.IWebviewPanelOptions): Promise<void>;
$undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void;
$applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void;
$undoEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void;
$applyEdits(resource: UriComponents, viewType: string, editIds: readonly number[]): void;
$disposeEdits(editIds: readonly number[]): void;
$onSave(resource: UriComponents, viewType: string): Promise<void>;
$onSaveAs(resource: UriComponents, viewType: string, targetResource: UriComponents): Promise<void>;
@@ -783,8 +785,10 @@ export interface MainThreadWindowShape extends IDisposable {
export interface MainThreadTunnelServiceShape extends IDisposable {
$openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
$getTunnels(): Promise<TunnelDescription[]>;
$registerCandidateFinder(): Promise<void>;
$setTunnelProvider(): Promise<void>;
$setCandidateFilter(): Promise<void>;
}
// -- extension host
@@ -999,10 +1003,14 @@ export const enum ISuggestDataDtoField {
additionalTextEdits = 'l',
command = 'm',
kindModifier = 'n',
// to merge into label
label2 = 'o',
}
export interface ISuggestDataDto {
[ISuggestDataDtoField.label]: string;
[ISuggestDataDtoField.label2]?: string | modes.CompletionItemLabel;
[ISuggestDataDtoField.kind]: modes.CompletionItemKind;
[ISuggestDataDtoField.detail]?: string;
[ISuggestDataDtoField.documentation]?: string | IMarkdownString;
@@ -1025,7 +1033,6 @@ export interface ISuggestResultDto {
a: { insert: IRange, replace: IRange; };
b: ISuggestDataDto[];
c?: true;
d?: true;
}
export interface ISignatureHelpDto {
@@ -1065,25 +1072,22 @@ export interface IWorkspaceSymbolsDto extends IdObject {
symbols: IWorkspaceSymbolDto[];
}
export interface IResourceFileEditDto {
export interface IWorkspaceFileEditDto {
oldUri?: UriComponents;
newUri?: UriComponents;
options?: {
overwrite?: boolean;
ignoreIfExists?: boolean;
ignoreIfNotExists?: boolean;
recursive?: boolean;
};
options?: modes.WorkspaceFileEditOptions
metadata?: modes.WorkspaceEditMetadata;
}
export interface IResourceTextEditDto {
export interface IWorkspaceTextEditDto {
resource: UriComponents;
edit: modes.TextEdit;
modelVersionId?: number;
edits: modes.TextEdit[];
metadata?: modes.WorkspaceEditMetadata;
}
export interface IWorkspaceEditDto {
edits: Array<IResourceFileEditDto | IResourceTextEditDto>;
edits: Array<IWorkspaceFileEditDto | IWorkspaceTextEditDto>;
// todo@joh reject should go into rename
rejectReason?: string;
@@ -1092,11 +1096,11 @@ export interface IWorkspaceEditDto {
export function reviveWorkspaceEditDto(data: IWorkspaceEditDto | undefined): modes.WorkspaceEdit {
if (data && data.edits) {
for (const edit of data.edits) {
if (typeof (<IResourceTextEditDto>edit).resource === 'object') {
(<IResourceTextEditDto>edit).resource = URI.revive((<IResourceTextEditDto>edit).resource);
if (typeof (<IWorkspaceTextEditDto>edit).resource === 'object') {
(<IWorkspaceTextEditDto>edit).resource = URI.revive((<IWorkspaceTextEditDto>edit).resource);
} else {
(<IResourceFileEditDto>edit).newUri = URI.revive((<IResourceFileEditDto>edit).newUri);
(<IResourceFileEditDto>edit).oldUri = URI.revive((<IResourceFileEditDto>edit).oldUri);
(<IWorkspaceFileEditDto>edit).newUri = URI.revive((<IWorkspaceFileEditDto>edit).newUri);
(<IWorkspaceFileEditDto>edit).oldUri = URI.revive((<IWorkspaceFileEditDto>edit).oldUri);
}
}
}
@@ -1419,8 +1423,10 @@ export interface MainThreadThemingShape extends IDisposable {
export interface ExtHostTunnelServiceShape {
$findCandidatePorts(): Promise<{ host: string, port: number, detail: string }[]>;
$filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<boolean[]>;
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined;
$closeTunnel(remote: { host: string, port: number }): Promise<void>;
$onDidTunnelsChange(): Promise<void>;
}
// --- proxy identifiers

View File

@@ -46,7 +46,7 @@ type ConfigurationInspect<T> = {
workspaceLanguageValue?: T;
workspaceFolderLanguageValue?: T;
languages?: string[];
languageIds?: string[];
};
function isUri(thing: any): thing is vscode.Uri {
@@ -267,7 +267,7 @@ export class ExtHostConfigProvider {
workspaceLanguageValue: config.workspace?.override,
workspaceFolderLanguageValue: config.workspaceFolder?.override,
languages: config.overrideIdentifiers
languageIds: config.overrideIdentifiers
};
}
return undefined;

View File

@@ -7,7 +7,7 @@ import { Event } from 'vs/base/common/event';
import { URI, UriComponents } from 'vs/base/common/uri';
import { sequence } from 'vs/base/common/async';
import { illegalState } from 'vs/base/common/errors';
import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IResourceTextEditDto } from 'vs/workbench/api/common/extHost.protocol';
import { ExtHostDocumentSaveParticipantShape, MainThreadTextEditorsShape, IWorkspaceEditDto } from 'vs/workbench/api/common/extHost.protocol';
import { TextEdit } from 'vs/workbench/api/common/extHostTypes';
import { Range, TextDocumentSaveReason, EndOfLine } from 'vs/workbench/api/common/extHostTypeConverters';
import { ExtHostDocuments } from 'vs/workbench/api/common/extHostDocuments';
@@ -141,19 +141,17 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
});
}).then(values => {
const resourceEdit: IResourceTextEditDto = {
resource: document.uri,
edits: []
};
const dto: IWorkspaceEditDto = { edits: [] };
for (const value of values) {
if (Array.isArray(value) && (<vscode.TextEdit[]>value).every(e => e instanceof TextEdit)) {
for (const { newText, newEol, range } of value) {
resourceEdit.edits.push({
range: range && Range.from(range),
text: newText,
eol: newEol && EndOfLine.from(newEol)
dto.edits.push({
resource: document.uri,
edit: {
range: range && Range.from(range),
text: newText,
eol: newEol && EndOfLine.from(newEol)
}
});
}
}
@@ -161,12 +159,12 @@ export class ExtHostDocumentSaveParticipant implements ExtHostDocumentSavePartic
// apply edits if any and if document
// didn't change somehow in the meantime
if (resourceEdit.edits.length === 0) {
if (dto.edits.length === 0) {
return undefined;
}
if (version === document.version) {
return this._mainThreadEditors.$tryApplyWorkspaceEdit({ edits: [resourceEdit] });
return this._mainThreadEditors.$tryApplyWorkspaceEdit(dto);
}
return Promise.reject(new Error('concurrent_edits'));

View File

@@ -645,7 +645,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
try {
const result = await resolver.resolve(remoteAuthority, { resolveAttempt });
this._disposables.add(await this._extHostTunnelService.setForwardPortProvider(resolver));
this._disposables.add(await this._extHostTunnelService.setTunnelExtensionFunctions(resolver));
// Split merged API result into separate authority/options
const authority: ResolvedAuthority = {
@@ -662,7 +662,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
value: {
authority,
options,
tunnelInformation: { environmentTunnels: result.environmentTunnels, hideCandidatePorts: result.hideCandidatePorts }
tunnelInformation: { environmentTunnels: result.environmentTunnels }
}
};
} catch (err) {

View File

@@ -8,7 +8,7 @@ import { IRelativePattern, parse } from 'vs/base/common/glob';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ExtHostDocumentsAndEditors } from 'vs/workbench/api/common/extHostDocumentsAndEditors';
import type * as vscode from 'vscode';
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, IResourceFileEditDto, IResourceTextEditDto } from './extHost.protocol';
import { ExtHostFileSystemEventServiceShape, FileSystemEvents, IMainContext, MainContext, MainThreadTextEditorsShape, IWorkspaceFileEditDto, IWorkspaceTextEditDto } from './extHost.protocol';
import * as typeConverter from './extHostTypeConverters';
import { Disposable, WorkspaceEdit } from './extHostTypes';
import { IExtensionDescription } from 'vs/platform/extensions/common/extensions';
@@ -219,7 +219,7 @@ export class ExtHostFileSystemEventService implements ExtHostFileSystemEventServ
if (edits.length > 0) {
// flatten all WorkspaceEdits collected via waitUntil-call
// and apply them in one go.
const allEdits = new Array<Array<IResourceFileEditDto | IResourceTextEditDto>>();
const allEdits = new Array<Array<IWorkspaceFileEditDto | IWorkspaceTextEditDto>>();
for (let edit of edits) {
let { edits } = typeConverter.WorkspaceEdit.from(edit, this._extHostDocumentsAndEditors);
allEdits.push(edits);

View File

@@ -30,6 +30,7 @@ import { VSBuffer } from 'vs/base/common/buffer';
import { encodeSemanticTokensDto } from 'vs/workbench/api/common/shared/semanticTokens';
import { IdGenerator } from 'vs/base/common/idGenerator';
import { IExtHostApiDeprecationService } from 'vs/workbench/api/common/extHostApiDeprecationService';
import { Cache } from './cache';
// --- adapter
@@ -822,8 +823,7 @@ class SuggestAdapter {
x: pid,
b: [],
a: { replace: typeConvert.Range.from(replaceRange), insert: typeConvert.Range.from(insertRange) },
c: list.isIncomplete || undefined,
d: list.isDetailsResolved || undefined
c: list.isIncomplete || undefined
};
for (let i = 0; i < list.items.length; i++) {
@@ -912,6 +912,7 @@ class SuggestAdapter {
x: id,
//
[extHostProtocol.ISuggestDataDtoField.label]: item.label,
[extHostProtocol.ISuggestDataDtoField.label2]: item.label2,
[extHostProtocol.ISuggestDataDtoField.kind]: typeConvert.CompletionItemKind.from(item.kind),
[extHostProtocol.ISuggestDataDtoField.kindModifier]: item.tags && item.tags.map(typeConvert.CompletionItemTag.from),
[extHostProtocol.ISuggestDataDtoField.detail]: item.detail,
@@ -1064,40 +1065,6 @@ class SignatureHelpAdapter {
}
}
class Cache<T> {
private static readonly enableDebugLogging = false;
private readonly _data = new Map<number, readonly T[]>();
private _idPool = 1;
constructor(
private readonly id: string
) { }
add(item: readonly T[]): number {
const id = this._idPool++;
this._data.set(id, item);
this.logDebugInfo();
return id;
}
get(pid: number, id: number): T | undefined {
return this._data.has(pid) ? this._data.get(pid)![id] : undefined;
}
delete(id: number) {
this._data.delete(id);
this.logDebugInfo();
}
private logDebugInfo() {
if (!Cache.enableDebugLogging) {
return;
}
console.log(`${this.id} cache size — ${this._data.size}`);
}
}
class LinkProviderAdapter {
private _cache = new Cache<vscode.DocumentLink>('DocumentLink');

View File

@@ -8,6 +8,7 @@ import { createDecorator } from 'vs/platform/instantiation/common/instantiation'
import type * as vscode from 'vscode';
import { RemoteTunnel, TunnelOptions } from 'vs/platform/remote/common/tunnel';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Emitter } from 'vs/base/common/event';
export interface TunnelDto {
remoteAddress: { port: number, host: string };
@@ -31,21 +32,31 @@ export interface Tunnel extends vscode.Disposable {
export interface IExtHostTunnelService extends ExtHostTunnelServiceShape {
readonly _serviceBrand: undefined;
openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined>;
setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
getTunnels(): Promise<vscode.TunnelDescription[]>;
onDidTunnelsChange: vscode.Event<void>;
setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable>;
}
export const IExtHostTunnelService = createDecorator<IExtHostTunnelService>('IExtHostTunnelService');
export class ExtHostTunnelService implements IExtHostTunnelService {
_serviceBrand: undefined;
onDidTunnelsChange: vscode.Event<void> = (new Emitter<void>()).event;
async openTunnel(forward: TunnelOptions): Promise<vscode.Tunnel | undefined> {
return undefined;
}
async getTunnels(): Promise<vscode.TunnelDescription[]> {
return [];
}
async $findCandidatePorts(): Promise<{ host: string, port: number; detail: string; }[]> {
return [];
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }
async $filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<boolean[]> {
return candidates.map(() => true);
}
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> { return { dispose: () => { } }; }
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined { return undefined; }
async $closeTunnel(remote: { host: string, port: number }): Promise<void> { }
async $onDidTunnelsChange(): Promise<void> { }
}

View File

@@ -483,15 +483,29 @@ export namespace WorkspaceEdit {
const result: extHostProtocol.IWorkspaceEditDto = {
edits: []
};
for (const entry of (value as types.WorkspaceEdit)._allEntries()) {
const [uri, uriOrEdits] = entry;
if (Array.isArray(uriOrEdits)) {
// text edits
const doc = documents && uri ? documents.getDocument(uri) : undefined;
result.edits.push(<extHostProtocol.IResourceTextEditDto>{ resource: uri, modelVersionId: doc && doc.version, edits: uriOrEdits.map(TextEdit.from) });
} else {
// resource edits
result.edits.push(<extHostProtocol.IResourceFileEditDto>{ oldUri: uri, newUri: uriOrEdits, options: entry[2] });
if (value instanceof types.WorkspaceEdit) {
for (let entry of value.allEntries()) {
if (entry._type === 1) {
// file operation
result.edits.push(<extHostProtocol.IWorkspaceFileEditDto>{
oldUri: entry.from,
newUri: entry.to,
options: entry.options,
metadata: entry.metadata
});
} else {
// text edits
const doc = documents?.getDocument(entry.uri);
result.edits.push(<extHostProtocol.IWorkspaceTextEditDto>{
resource: entry.uri,
edit: TextEdit.from(entry.edit),
modelVersionId: doc?.version,
metadata: entry.metadata
});
}
}
}
return result;
@@ -500,16 +514,17 @@ export namespace WorkspaceEdit {
export function to(value: extHostProtocol.IWorkspaceEditDto) {
const result = new types.WorkspaceEdit();
for (const edit of value.edits) {
if (Array.isArray((<extHostProtocol.IResourceTextEditDto>edit).edits)) {
result.set(
URI.revive((<extHostProtocol.IResourceTextEditDto>edit).resource),
<types.TextEdit[]>(<extHostProtocol.IResourceTextEditDto>edit).edits.map(TextEdit.to)
if ((<extHostProtocol.IWorkspaceTextEditDto>edit).edit) {
result.replace(
URI.revive((<extHostProtocol.IWorkspaceTextEditDto>edit).resource),
Range.to((<extHostProtocol.IWorkspaceTextEditDto>edit).edit.range),
(<extHostProtocol.IWorkspaceTextEditDto>edit).edit.text
);
} else {
result.renameFile(
URI.revive((<extHostProtocol.IResourceFileEditDto>edit).oldUri!),
URI.revive((<extHostProtocol.IResourceFileEditDto>edit).newUri!),
(<extHostProtocol.IResourceFileEditDto>edit).options
URI.revive((<extHostProtocol.IWorkspaceFileEditDto>edit).oldUri!),
URI.revive((<extHostProtocol.IWorkspaceFileEditDto>edit).newUri!),
(<extHostProtocol.IWorkspaceFileEditDto>edit).options
);
}
}
@@ -832,7 +847,12 @@ export namespace CompletionItemKind {
export namespace CompletionItem {
export function to(suggestion: modes.CompletionItem, converter?: CommandsConverter): types.CompletionItem {
const result = new types.CompletionItem(suggestion.label);
const result = new types.CompletionItem(typeof suggestion.label === 'string' ? suggestion.label : suggestion.label.name);
if (typeof suggestion.label !== 'string') {
result.label2 = suggestion.label;
}
result.insertText = suggestion.insertText;
result.kind = CompletionItemKind.to(suggestion.kind);
result.tags = suggestion.tags && suggestion.tags.map(CompletionItemTag.to);

View File

@@ -564,7 +564,6 @@ export class TextEdit {
}
}
export interface IFileOperationOptions {
overwrite?: boolean;
ignoreIfExists?: boolean;
@@ -577,12 +576,14 @@ export interface IFileOperation {
from?: URI;
to?: URI;
options?: IFileOperationOptions;
metadata?: vscode.WorkspaceEditMetadata;
}
export interface IFileTextEdit {
_type: 2;
uri: URI;
edit: TextEdit;
metadata?: vscode.WorkspaceEditMetadata;
}
@es5ClassCompat
@@ -590,28 +591,28 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
private _edits = new Array<IFileOperation | IFileTextEdit>();
renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }): void {
this._edits.push({ _type: 1, from, to, options });
renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void {
this._edits.push({ _type: 1, from, to, options, metadata });
}
createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }): void {
this._edits.push({ _type: 1, from: undefined, to: uri, options });
createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void {
this._edits.push({ _type: 1, from: undefined, to: uri, options, metadata });
}
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }): void {
this._edits.push({ _type: 1, from: uri, to: undefined, options });
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void {
this._edits.push({ _type: 1, from: uri, to: undefined, options, metadata });
}
replace(uri: URI, range: Range, newText: string): void {
this._edits.push({ _type: 2, uri, edit: new TextEdit(range, newText) });
replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditMetadata): void {
this._edits.push({ _type: 2, uri, edit: new TextEdit(range, newText), metadata });
}
insert(resource: URI, position: Position, newText: string): void {
this.replace(resource, new Range(position, position), newText);
insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditMetadata): void {
this.replace(resource, new Range(position, position), newText, metadata);
}
delete(resource: URI, range: Range): void {
this.replace(resource, range, '');
delete(resource: URI, range: Range, metadata?: vscode.WorkspaceEditMetadata): void {
this.replace(resource, range, '', metadata);
}
has(uri: URI): boolean {
@@ -663,18 +664,22 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
return values(textEdits);
}
_allEntries(): ([URI, TextEdit[]] | [URI?, URI?, IFileOperationOptions?])[] {
const res: ([URI, TextEdit[]] | [URI?, URI?, IFileOperationOptions?])[] = [];
for (let edit of this._edits) {
if (edit._type === 1) {
res.push([edit.from, edit.to, edit.options]);
} else {
res.push([edit.uri, [edit.edit]]);
}
}
return res;
allEntries(): ReadonlyArray<IFileTextEdit | IFileOperation> {
return this._edits;
}
// _allEntries(): ([URI, TextEdit] | [URI?, URI?, IFileOperationOptions?])[] {
// const res: ([URI, TextEdit] | [URI?, URI?, IFileOperationOptions?])[] = [];
// for (let edit of this._edits) {
// if (edit._type === 1) {
// res.push([edit.from, edit.to, edit.options]);
// } else {
// res.push([edit.uri, edit.edit]);
// }
// }
// return res;
// }
get size(): number {
return this.entries().length;
}
@@ -1351,10 +1356,19 @@ export enum CompletionItemTag {
Deprecated = 1,
}
export interface CompletionItemLabel {
name: string;
// signature?: string; // parameters
// qualifier?: string;
type?: string;
}
@es5ClassCompat
export class CompletionItem implements vscode.CompletionItem {
label: string;
label2?: CompletionItemLabel;
kind?: CompletionItemKind;
tags?: CompletionItemTag[];
detail?: string;
@@ -1378,6 +1392,7 @@ export class CompletionItem implements vscode.CompletionItem {
toJSON(): any {
return {
label: this.label,
label2: this.label2,
kind: this.kind && CompletionItemKind[this.kind],
detail: this.detail,
documentation: this.documentation,
@@ -1394,7 +1409,6 @@ export class CompletionItem implements vscode.CompletionItem {
export class CompletionList {
isIncomplete?: boolean;
isDetailsResolved?: boolean;
items: vscode.CompletionItem[];
constructor(items: vscode.CompletionItem[] = [], isIncomplete: boolean = false) {

View File

@@ -15,6 +15,7 @@ import { IExtHostWorkspace } from 'vs/workbench/api/common/extHostWorkspace';
import { EditorViewColumn } from 'vs/workbench/api/common/shared/editor';
import { asWebviewUri, WebviewInitData } from 'vs/workbench/api/common/shared/webview';
import type * as vscode from 'vscode';
import { Cache } from './cache';
import { ExtHostWebviewsShape, IMainContext, MainContext, MainThreadWebviewsShape, WebviewEditorCapabilities, WebviewPanelHandle, WebviewPanelViewStateData } from './extHost.protocol';
import { Disposable as VSCodeDisposable } from './extHostTypes';
@@ -251,8 +252,18 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
private readonly _proxy: MainThreadWebviewsShape;
private readonly _webviewPanels = new Map<WebviewPanelHandle, ExtHostWebviewEditor>();
private readonly _serializers = new Map<string, { readonly serializer: vscode.WebviewPanelSerializer, readonly extension: IExtensionDescription }>();
private readonly _editorProviders = new Map<string, { readonly provider: vscode.WebviewCustomEditorProvider, readonly extension: IExtensionDescription }>();
private readonly _serializers = new Map<string, {
readonly serializer: vscode.WebviewPanelSerializer;
readonly extension: IExtensionDescription;
}>();
private readonly _editorProviders = new Map<string, {
readonly provider: vscode.WebviewCustomEditorProvider;
readonly extension: IExtensionDescription;
}>();
private readonly _edits = new Cache<unknown>('edits');
constructor(
mainContext: IMainContext,
@@ -312,11 +323,14 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
if (this._editorProviders.has(viewType)) {
throw new Error(`Editor provider for '${viewType}' already registered`);
}
this._editorProviders.set(viewType, { extension, provider, });
this._proxy.$registerEditorProvider({ id: extension.identifier, location: extension.extensionLocation }, viewType, options || {}, this.getCapabilites(provider));
// Hook up events
provider?.editingDelegate?.onEdit(({ edit, resource }) => {
this._proxy.$onEdit(resource, viewType, edit);
const id = this._edits.add([edit]);
this._proxy.$onEdit(resource, viewType, id);
});
return new VSCodeDisposable(() => {
@@ -426,14 +440,32 @@ export class ExtHostWebviews implements ExtHostWebviewsShape {
await provider.resolveWebviewEditor(revivedResource, revivedPanel);
}
$undoEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void {
$undoEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void {
const provider = this.getEditorProvider(viewType);
provider?.editingDelegate?.undoEdits(URI.revive(resource), edits);
if (!provider?.editingDelegate) {
return;
}
const resource = URI.revive(resourceComponents);
const edits = editIds.map(id => this._edits.get(id, 0));
provider.editingDelegate.undoEdits(resource, edits);
}
$applyEdits(resource: UriComponents, viewType: string, edits: readonly any[]): void {
$applyEdits(resourceComponents: UriComponents, viewType: string, editIds: readonly number[]): void {
const provider = this.getEditorProvider(viewType);
provider?.editingDelegate?.applyEdits(URI.revive(resource), edits);
if (!provider?.editingDelegate) {
return;
}
const resource = URI.revive(resourceComponents);
const edits = editIds.map(id => this._edits.get(id, 0));
provider.editingDelegate.applyEdits(resource, edits);
}
$disposeEdits(editIds: readonly number[]): void {
for (const edit of editIds) {
this._edits.delete(edit);
}
}
async $onSave(resource: UriComponents, viewType: string): Promise<void> {

View File

@@ -37,7 +37,10 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
readonly _serviceBrand: undefined;
private readonly _proxy: MainThreadTunnelServiceShape;
private _forwardPortProvider: ((tunnelOptions: TunnelOptions) => Thenable<vscode.Tunnel> | undefined) | undefined;
private _showCandidatePort: (host: string, port: number, detail: string) => Thenable<boolean> = () => { return Promise.resolve(true); };
private _extensionTunnels: Map<string, Map<number, vscode.Tunnel>> = new Map();
private _onDidTunnelsChange: Emitter<void> = new Emitter<void>();
onDidTunnelsChange: vscode.Event<void> = this._onDidTunnelsChange.event;
constructor(
@IExtHostRpcService extHostRpc: IExtHostRpcService,
@@ -61,14 +64,30 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
return undefined;
}
async getTunnels(): Promise<vscode.TunnelDescription[]> {
return this._proxy.$getTunnels();
}
registerCandidateFinder(): Promise<void> {
return this._proxy.$registerCandidateFinder();
}
async setForwardPortProvider(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
if (provider && provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider();
$filterCandidates(candidates: { host: string, port: number, detail: string }[]): Promise<boolean[]> {
return Promise.all(candidates.map(candidate => {
return this._showCandidatePort(candidate.host, candidate.port, candidate.detail);
}));
}
async setTunnelExtensionFunctions(provider: vscode.RemoteAuthorityResolver | undefined): Promise<IDisposable> {
if (provider) {
if (provider.showCandidatePort) {
this._showCandidatePort = provider.showCandidatePort;
await this._proxy.$setCandidateFilter();
}
if (provider.tunnelFactory) {
this._forwardPortProvider = provider.tunnelFactory;
await this._proxy.$setTunnelProvider();
}
} else {
this._forwardPortProvider = undefined;
}
@@ -87,6 +106,10 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
}
}
async $onDidTunnelsChange(): Promise<void> {
this._onDidTunnelsChange.fire();
}
$forwardPort(tunnelOptions: TunnelOptions): Promise<TunnelDto> | undefined {
if (this._forwardPortProvider) {
const providedPort = this._forwardPortProvider!(tunnelOptions);
@@ -128,9 +151,7 @@ export class ExtHostTunnelService extends Disposable implements IExtHostTunnelSe
const childStat = fs.statSync(childUri.fsPath);
if (childStat.isDirectory() && !isNaN(pid)) {
const cwd = fs.readlinkSync(resources.joinPath(childUri, 'cwd').fsPath);
const rawCmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
const nullIndex = rawCmd.indexOf('\0');
const cmd = rawCmd.substr(0, nullIndex > 0 ? nullIndex : rawCmd.length).trim();
const cmd = fs.readFileSync(resources.joinPath(childUri, 'cmdline').fsPath, 'utf8');
processes.push({ pid, cwd, cmd });
}
} catch (e) {