Archived
Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781 (#8649)
* Merge from vscode a4177f50c475fc0fa278a78235e3bee9ffdec781 * distro * fix tests
This commit is contained in:
@@ -33,14 +33,14 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
export class CategoryView extends ViewletPane {
|
||||
export class CategoryView extends ViewPane {
|
||||
|
||||
constructor(
|
||||
private contentElement: HTMLElement,
|
||||
private size: number,
|
||||
options: IViewletPaneOptions,
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
|
||||
@@ -49,9 +49,9 @@ import { IOEShimService } from 'sql/workbench/contrib/objectExplorer/browser/obj
|
||||
import { NodeContextKey } from 'sql/workbench/contrib/dataExplorer/browser/nodeContext';
|
||||
import { UserCancelledConnectionError } from 'sql/base/common/errors';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
export class CustomTreeViewPanel extends ViewletPane {
|
||||
export class CustomTreeViewPanel extends ViewPane {
|
||||
|
||||
private treeView: ITreeView;
|
||||
|
||||
@@ -63,7 +63,7 @@ export class CustomTreeViewPanel extends ViewletPane {
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
) {
|
||||
super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
|
||||
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
|
||||
const { treeView } = (<ITreeViewDescriptor>Registry.as<IViewsRegistry>(Extensions.ViewsRegistry).getView(options.id));
|
||||
this.treeView = treeView as ITreeView;
|
||||
this._register(this.treeView.onDidChangeActions(() => this.updateActions(), this));
|
||||
|
||||
@@ -35,14 +35,14 @@ import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
|
||||
import { IViewPaneOptions, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
class AccountPanel extends ViewletPane {
|
||||
class AccountPanel extends ViewPane {
|
||||
public index: number;
|
||||
private accountList: List<azdata.Account>;
|
||||
|
||||
constructor(
|
||||
private options: IViewletPaneOptions,
|
||||
private options: IViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
|
||||
@@ -19,9 +19,9 @@ import {
|
||||
import { IObjectExplorerService } from 'sql/workbench/services/objectExplorer/browser/objectExplorerService';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ITree } from 'vs/base/parts/tree/browser/tree';
|
||||
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
export class ConnectionViewletPanel extends ViewletPane {
|
||||
export class ConnectionViewletPanel extends ViewPane {
|
||||
|
||||
public static readonly ID = 'dataExplorer.servers';
|
||||
|
||||
@@ -40,7 +40,7 @@ export class ConnectionViewletPanel extends ViewletPane {
|
||||
@IObjectExplorerService private readonly objectExplorerService: IObjectExplorerService,
|
||||
@IContextKeyService contextKeyService: IContextKeyService
|
||||
) {
|
||||
super({ ...(options as IViewletPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
|
||||
super({ ...(options as IViewPaneOptions), ariaHeaderLabel: options.title }, keybindingService, contextMenuService, configurationService, contextKeyService);
|
||||
this._addServerAction = this.instantiationService.createInstance(AddServerAction,
|
||||
AddServerAction.ID,
|
||||
AddServerAction.LABEL);
|
||||
|
||||
@@ -11,7 +11,7 @@ import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { ViewContainerViewlet, IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IViewletViewOptions } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||
import { IContextMenuService } from 'vs/platform/contextview/browser/contextView';
|
||||
@@ -23,10 +23,10 @@ import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/la
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IMenuService, MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ShowViewletAction } from 'vs/workbench/browser/viewlet';
|
||||
import { ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { ViewletPane } from 'vs/workbench/browser/parts/views/paneViewlet';
|
||||
import { ViewPaneContainer, ViewPane } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
export const VIEWLET_ID = 'workbench.view.connections';
|
||||
|
||||
@@ -72,7 +72,23 @@ export class DataExplorerViewletViewsContribution implements IWorkbenchContribut
|
||||
}
|
||||
}
|
||||
|
||||
export class DataExplorerViewlet extends ViewContainerViewlet {
|
||||
export class DataExplorerViewlet extends Viewlet {
|
||||
constructor(
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IStorageService protected storageService: IStorageService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService protected contextMenuService: IContextMenuService,
|
||||
@IExtensionService protected extensionService: IExtensionService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IWorkbenchLayoutService protected layoutService: IWorkbenchLayoutService,
|
||||
@IConfigurationService protected configurationService: IConfigurationService
|
||||
) {
|
||||
super(VIEWLET_ID, instantiationService.createInstance(DataExplorerViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
export class DataExplorerViewPaneContainer extends ViewPaneContainer {
|
||||
private root: HTMLElement;
|
||||
|
||||
private dataSourcesBox: HTMLElement;
|
||||
@@ -90,7 +106,7 @@ export class DataExplorerViewlet extends ViewContainerViewlet {
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService
|
||||
) {
|
||||
super(VIEWLET_ID, `${VIEWLET_ID}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
super(VIEWLET_ID, `${VIEWLET_ID}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
|
||||
}
|
||||
|
||||
create(parent: HTMLElement): void {
|
||||
@@ -134,13 +150,13 @@ export class DataExplorerViewlet extends ViewContainerViewlet {
|
||||
return actions;
|
||||
}
|
||||
|
||||
protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewletPane[] {
|
||||
protected onDidAddViews(added: IAddedViewDescriptorRef[]): ViewPane[] {
|
||||
const addedViews = super.onDidAddViews(added);
|
||||
return addedViews;
|
||||
}
|
||||
|
||||
protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewletPane {
|
||||
let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewletPane;
|
||||
protected createView(viewDescriptor: IViewDescriptor, options: IViewletViewOptions): ViewPane {
|
||||
let viewletPanel = this.instantiationService.createInstance(viewDescriptor.ctorDescriptor.ctor, options) as ViewPane;
|
||||
this._register(viewletPanel);
|
||||
return viewletPanel;
|
||||
}
|
||||
|
||||
@@ -1,55 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import * as Platform from 'vs/platform/registry/common/platform';
|
||||
import { ViewletDescriptor, Extensions, Viewlet, ViewletRegistry } from 'vs/workbench/browser/viewlet';
|
||||
import * as Types from 'vs/base/common/types';
|
||||
|
||||
suite('Data Explorer Viewlet', () => {
|
||||
|
||||
class DataExplorerTestViewlet extends Viewlet {
|
||||
|
||||
constructor() {
|
||||
super('dataExplorer', null, null, null, null, null);
|
||||
}
|
||||
|
||||
public layout(dimension: any): void {
|
||||
throw new Error('Method not implemented.');
|
||||
}
|
||||
}
|
||||
|
||||
test('ViewletDescriptor API', function () {
|
||||
let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 1);
|
||||
assert.strictEqual(d.id, 'id');
|
||||
assert.strictEqual(d.name, 'name');
|
||||
assert.strictEqual(d.cssClass, 'class');
|
||||
assert.strictEqual(d.order, 1);
|
||||
});
|
||||
|
||||
test('Editor Aware ViewletDescriptor API', function () {
|
||||
let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
|
||||
assert.strictEqual(d.id, 'id');
|
||||
assert.strictEqual(d.name, 'name');
|
||||
|
||||
d = ViewletDescriptor.create(DataExplorerTestViewlet, 'id', 'name', 'class', 5);
|
||||
assert.strictEqual(d.id, 'id');
|
||||
assert.strictEqual(d.name, 'name');
|
||||
});
|
||||
|
||||
test('Data Explorer Viewlet extension point and registration', function () {
|
||||
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).registerViewlet));
|
||||
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet));
|
||||
assert(Types.isFunction(Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets));
|
||||
|
||||
let oldCount = Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets().length;
|
||||
let d = ViewletDescriptor.create(DataExplorerTestViewlet, 'dataExplorer-test-id', 'name');
|
||||
Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).registerViewlet(d);
|
||||
let retrieved = Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlet('dataExplorer-test-id');
|
||||
assert(d === retrieved);
|
||||
let newCount = Platform.Registry.as<ViewletRegistry>(Extensions.Viewlets).getViewlets().length;
|
||||
assert.equal(oldCount + 1, newCount);
|
||||
});
|
||||
});
|
||||
@@ -6,10 +6,11 @@
|
||||
import { localize } from 'vs/nls';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IExtensionsWorkbenchService, VIEWLET_ID, IExtensionsViewlet } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtensionsWorkbenchService, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { IExtensionRecommendation } from 'vs/workbench/services/extensionManagement/common/extensionManagement';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { PagedModel } from 'vs/base/common/paging';
|
||||
import { ExtensionsViewlet, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
|
||||
|
||||
function getScenarioID(scenarioType: string) {
|
||||
return 'workbench.extensions.action.show' + scenarioType;
|
||||
@@ -25,9 +26,8 @@ export class ShowRecommendedExtensionsByScenarioAction extends Action {
|
||||
|
||||
run(): Promise<void> {
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet as IExtensionsViewlet)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@' + this.scenarioType);
|
||||
.then((viewlet: ExtensionsViewlet) => {
|
||||
(viewlet.getViewPaneContainer() as ExtensionsViewPaneContainer).search('@' + this.scenarioType);
|
||||
viewlet.focus();
|
||||
});
|
||||
}
|
||||
@@ -51,9 +51,8 @@ export class InstallRecommendedExtensionsByScenarioAction extends Action {
|
||||
run(): Promise<any> {
|
||||
if (!this.recommendations.length) { return Promise.resolve(); }
|
||||
return this.viewletService.openViewlet(VIEWLET_ID, true)
|
||||
.then(viewlet => viewlet as IExtensionsViewlet)
|
||||
.then(viewlet => {
|
||||
viewlet.search('@' + this.scenarioType);
|
||||
.then((viewlet: ExtensionsViewlet) => {
|
||||
(viewlet.getViewPaneContainer() as ExtensionsViewPaneContainer).search('@' + this.scenarioType);
|
||||
viewlet.focus();
|
||||
const names = this.recommendations.map(({ extensionId }) => extensionId);
|
||||
return this.extensionWorkbenchService.queryGallery({ names, source: 'install-' + this.scenarioType }, CancellationToken.None).then(pager => {
|
||||
|
||||
@@ -39,7 +39,7 @@ import { ISingleNotebookEditOperation } from 'sql/workbench/api/common/sqlExtHos
|
||||
import { IConnectionDialogService } from 'sql/workbench/services/connection/common/connectionDialogService';
|
||||
import { ICapabilitiesService } from 'sql/platform/capabilities/common/capabilitiesService';
|
||||
import { CellMagicMapper } from 'sql/workbench/contrib/notebook/browser/models/cellMagicMapper';
|
||||
import { IExtensionsViewlet, VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { VIEWLET_ID } from 'vs/workbench/contrib/extensions/common/extensions';
|
||||
import { CellModel } from 'sql/workbench/contrib/notebook/browser/models/cell';
|
||||
import { FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||
import { isValidBasename } from 'vs/base/common/extpath';
|
||||
@@ -55,6 +55,7 @@ import { isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IBootstrapParams } from 'sql/workbench/services/bootstrap/common/bootstrapParams';
|
||||
import { getErrorMessage } from 'vs/base/common/errors';
|
||||
import { find, firstIndex } from 'vs/base/common/arrays';
|
||||
import { ExtensionsViewlet, ExtensionsViewPaneContainer } from 'vs/workbench/contrib/extensions/browser/extensionsViewlet';
|
||||
|
||||
|
||||
export const NOTEBOOK_SELECTOR: string = 'notebook-component';
|
||||
@@ -343,8 +344,8 @@ export class NotebookComponent extends AngularDisposable implements OnInit, OnDe
|
||||
|
||||
private async openExtensionGallery(): Promise<void> {
|
||||
try {
|
||||
let viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true) as IExtensionsViewlet;
|
||||
viewlet.search('sql-vnext');
|
||||
let viewlet = await this.viewletService.openViewlet(VIEWLET_ID, true) as ExtensionsViewlet;
|
||||
(viewlet.getViewPaneContainer() as ExtensionsViewPaneContainer).search('sql-vnext');
|
||||
viewlet.focus();
|
||||
} catch (error) {
|
||||
this.notificationService.error(error.message);
|
||||
|
||||
@@ -21,10 +21,11 @@ import { getBaseLabel } from 'vs/base/common/labels';
|
||||
import { ShowFileInFolderAction, OpenFileInFolderAction } from 'sql/workbench/common/workspaceActions';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { getRootPath, resolveCurrentDirectory, resolveFilePath } from 'sql/platform/common/pathUtilities';
|
||||
import { IOutputService, IOutputChannelRegistry, IOutputChannel, Extensions as OutputExtensions } from 'vs/workbench/contrib/output/common/output';
|
||||
import { IOutputService, IOutputChannel } from 'vs/workbench/contrib/output/common/output';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IFileDialogService, FileFilter } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IOutputChannelRegistry, Extensions as OutputExtensions } from 'vs/workbench/services/output/common/output';
|
||||
|
||||
let prevSavePath: string;
|
||||
|
||||
|
||||
@@ -41,14 +41,14 @@ import { IInsightsConfigDetails } from 'sql/platform/dashboard/browser/insightRe
|
||||
import { TaskRegistry } from 'sql/platform/tasks/browser/tasksRegistry';
|
||||
import { ITextResourcePropertiesService } from 'vs/editor/common/services/resourceConfiguration';
|
||||
import { IAdsTelemetryService } from 'sql/platform/telemetry/common/telemetry';
|
||||
import { ViewletPane, IViewletPaneOptions } from 'vs/workbench/browser/parts/views/paneViewlet';
|
||||
import { onUnexpectedError } from 'vs/base/common/errors';
|
||||
import { ViewPane, IViewPaneOptions } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
const labelDisplay = nls.localize("insights.item", "Item");
|
||||
const valueDisplay = nls.localize("insights.value", "Value");
|
||||
const iconClass = 'codicon';
|
||||
|
||||
class InsightTableView<T> extends ViewletPane {
|
||||
class InsightTableView<T> extends ViewPane {
|
||||
private _table: Table<T>;
|
||||
public get table(): Table<T> {
|
||||
return this._table;
|
||||
@@ -58,7 +58,7 @@ class InsightTableView<T> extends ViewletPane {
|
||||
private columns: Slick.Column<T>[],
|
||||
private data: IDisposableDataProvider<T> | Array<T>,
|
||||
private tableOptions: Slick.GridOptions<T>,
|
||||
options: IViewletPaneOptions,
|
||||
options: IViewPaneOptions,
|
||||
@IKeybindingService keybindingService: IKeybindingService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
|
||||
Vendored
-10730
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@ import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { renderCodicons, markdownEscapeEscapedCodicons } from 'vs/base/common/codicons';
|
||||
|
||||
export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
|
||||
codeBlockRenderer?: (modeId: string, value: string) => Promise<string>;
|
||||
@@ -118,7 +119,7 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
}
|
||||
};
|
||||
renderer.paragraph = (text): string => {
|
||||
return `<p>${text}</p>`;
|
||||
return `<p>${markdown.supportThemeIcons ? renderCodicons(text) : text}</p>`;
|
||||
};
|
||||
|
||||
if (options.codeBlockRenderer) {
|
||||
@@ -192,7 +193,13 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
|
||||
allowedSchemes.push(Schemas.command);
|
||||
}
|
||||
|
||||
const renderedMarkdown = marked.parse(markdown.value, markedOptions);
|
||||
const renderedMarkdown = marked.parse(
|
||||
markdown.supportThemeIcons
|
||||
? markdownEscapeEscapedCodicons(markdown.value)
|
||||
: markdown.value,
|
||||
markedOptions
|
||||
);
|
||||
|
||||
element.innerHTML = insane(renderedMarkdown, {
|
||||
allowedSchemes,
|
||||
allowedAttributes: {
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 3.76345L5.80687 11.9351L5.08584 11.8927L1 7.29614L1.76345 6.61752L5.50997 10.8324L14.3214 3L15 3.76345Z" fill="#C5C5C5"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 278 B |
@@ -1,3 +0,0 @@
|
||||
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M15 3.76345L5.80687 11.9351L5.08584 11.8927L1 7.29614L1.76345 6.61752L5.50997 10.8324L14.3214 3L15 3.76345Z" fill="#424242"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 278 B |
@@ -44,10 +44,7 @@
|
||||
background-size: 16px !important;
|
||||
}
|
||||
|
||||
.monaco-custom-checkbox.monaco-simple-checkbox.checked {
|
||||
background: url('check-light.svg') center center no-repeat;
|
||||
}
|
||||
|
||||
.monaco-custom-checkbox.monaco-simple-checkbox.checked {
|
||||
background: url('check-dark.svg') center center no-repeat;
|
||||
/* hide check when unchecked */
|
||||
.monaco-custom-checkbox.monaco-simple-checkbox.unchecked:not(.checked)::before {
|
||||
visibility: hidden;;
|
||||
}
|
||||
|
||||
@@ -192,7 +192,7 @@ export class SimpleCheckbox extends Widget {
|
||||
constructor(private title: string, private isChecked: boolean) {
|
||||
super();
|
||||
|
||||
this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox' });
|
||||
this.checkbox = new Checkbox({ title: this.title, isChecked: this.isChecked, actionClassName: 'monaco-simple-checkbox codicon-check' });
|
||||
|
||||
this.domNode = this.checkbox.domNode;
|
||||
|
||||
|
||||
@@ -395,9 +395,9 @@
|
||||
.codicon-debug-breakpoint-function-unverified:before { content: "\eb87" }
|
||||
.codicon-debug-breakpoint-function:before { content: "\eb88" }
|
||||
.codicon-debug-breakpoint-function-disabled:before { content: "\eb88" }
|
||||
.codicon-debug-breakpoint-stackframe-active:before { content: "\eb89" }
|
||||
.codicon-debug-breakpoint-stackframe:before { content: "\eb8b" }
|
||||
.codicon-debug-breakpoint-stackframe-focused:before { content: "\eb8b" }
|
||||
.codicon-debug-stackframe-active:before { content: "\eb89" }
|
||||
.codicon-debug-stackframe:before { content: "\eb8b" }
|
||||
.codicon-debug-stackframe-focused:before { content: "\eb8b" }
|
||||
.codicon-debug-breakpoint-unsupported:before { content: "\eb8c" }
|
||||
.codicon-symbol-string:before { content: "\eb8d" }
|
||||
.codicon-debug-reverse-continue:before { content: "\eb8e" }
|
||||
|
||||
@@ -6,16 +6,7 @@
|
||||
import 'vs/css!./codicon/codicon';
|
||||
import 'vs/css!./codicon/codicon-animations';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
|
||||
function expand(text: string): string {
|
||||
return text.replace(/\$\((([a-z0-9\-]+?)(~([a-z0-9\-]*?))?)\)/gi, (_match, _g1, name, _g3, animation) => {
|
||||
return `<span class="codicon codicon-${name} ${animation ? `codicon-animation-${animation}` : ''}"></span>`;
|
||||
});
|
||||
}
|
||||
|
||||
export function renderCodicons(label: string): string {
|
||||
return expand(escape(label));
|
||||
}
|
||||
import { renderCodicons } from 'vs/base/common/codicons';
|
||||
|
||||
export class CodiconLabel {
|
||||
|
||||
@@ -24,7 +15,7 @@ export class CodiconLabel {
|
||||
) { }
|
||||
|
||||
set text(text: string) {
|
||||
this._container.innerHTML = renderCodicons(text || '');
|
||||
this._container.innerHTML = renderCodicons(escape(text ?? ''));
|
||||
}
|
||||
|
||||
set title(title: string) {
|
||||
|
||||
@@ -5,5 +5,5 @@
|
||||
|
||||
.context-view {
|
||||
position: absolute;
|
||||
z-index: 2000;
|
||||
}
|
||||
z-index: 2500;
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
max-width: 90%;
|
||||
min-height: 75px;
|
||||
padding: 10px;
|
||||
transform: translate3d(0px, 0px, 0px);
|
||||
}
|
||||
|
||||
/** Dialog: Title Actions Row */
|
||||
|
||||
@@ -46,6 +46,7 @@ interface ButtonMapEntry {
|
||||
|
||||
export class Dialog extends Disposable {
|
||||
private element: HTMLElement | undefined;
|
||||
private shadowElement: HTMLElement | undefined;
|
||||
private modal: HTMLElement | undefined;
|
||||
private buttonsContainer: HTMLElement | undefined;
|
||||
private messageDetailElement: HTMLElement | undefined;
|
||||
@@ -61,7 +62,8 @@ export class Dialog extends Disposable {
|
||||
constructor(private container: HTMLElement, private message: string, buttons: string[], private options: IDialogOptions) {
|
||||
super();
|
||||
this.modal = this.container.appendChild($(`.dialog-modal-block${options.type === 'pending' ? '.dimmed' : ''}`));
|
||||
this.element = this.modal.appendChild($('.dialog-box'));
|
||||
this.shadowElement = this.modal.appendChild($('.dialog-shadow'));
|
||||
this.element = this.shadowElement.appendChild($('.dialog-box'));
|
||||
hide(this.element);
|
||||
|
||||
// If no button is provided, default to OK
|
||||
@@ -249,10 +251,13 @@ export class Dialog extends Disposable {
|
||||
const shadowColor = style.dialogShadow ? `0 0px 8px ${style.dialogShadow}` : '';
|
||||
const border = style.dialogBorder ? `1px solid ${style.dialogBorder}` : '';
|
||||
|
||||
if (this.shadowElement) {
|
||||
this.shadowElement.style.boxShadow = shadowColor;
|
||||
}
|
||||
|
||||
if (this.element) {
|
||||
this.element.style.color = fgColor;
|
||||
this.element.style.backgroundColor = bgColor;
|
||||
this.element.style.boxShadow = shadowColor;
|
||||
this.element.style.border = border;
|
||||
|
||||
if (this.buttonGroup) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
|
||||
import { renderCodicons } from 'vs/base/common/codicons';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
|
||||
export interface IHighlight {
|
||||
@@ -65,13 +65,13 @@ export class HighlightedLabel {
|
||||
if (pos < highlight.start) {
|
||||
htmlContent += '<span>';
|
||||
const substring = this.text.substring(pos, highlight.start);
|
||||
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring);
|
||||
htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
|
||||
htmlContent += '</span>';
|
||||
pos = highlight.end;
|
||||
}
|
||||
htmlContent += '<span class="highlight">';
|
||||
const substring = this.text.substring(highlight.start, highlight.end);
|
||||
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring);
|
||||
htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
|
||||
htmlContent += '</span>';
|
||||
pos = highlight.end;
|
||||
}
|
||||
@@ -79,7 +79,7 @@ export class HighlightedLabel {
|
||||
if (pos < this.text.length) {
|
||||
htmlContent += '<span>';
|
||||
const substring = this.text.substring(pos);
|
||||
htmlContent += this.supportCodicons ? renderCodicons(substring) : escape(substring);
|
||||
htmlContent += this.supportCodicons ? renderCodicons(escape(substring)) : escape(substring);
|
||||
htmlContent += '</span>';
|
||||
}
|
||||
|
||||
|
||||
@@ -593,7 +593,7 @@ class BaseMenuActionViewItem extends BaseActionViewItem {
|
||||
|
||||
const isSelected = this.element && hasClass(this.element, 'focused');
|
||||
const fgColor = isSelected && this.menuStyle.selectionForegroundColor ? this.menuStyle.selectionForegroundColor : this.menuStyle.foregroundColor;
|
||||
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : this.menuStyle.backgroundColor;
|
||||
const bgColor = isSelected && this.menuStyle.selectionBackgroundColor ? this.menuStyle.selectionBackgroundColor : undefined;
|
||||
const border = isSelected && this.menuStyle.selectionBorderColor ? `thin solid ${this.menuStyle.selectionBorderColor}` : '';
|
||||
|
||||
if (this.item) {
|
||||
|
||||
@@ -1298,7 +1298,7 @@ export abstract class AbstractTree<T, TFilterData, TRef> implements IDisposable
|
||||
onDidModelSplice(() => null, null, this.disposables);
|
||||
|
||||
// Active nodes can change when the model changes or when focus or selection change.
|
||||
// We debouce it with 0 delay since these events may fire in the same stack and we only
|
||||
// We debounce it with 0 delay since these events may fire in the same stack and we only
|
||||
// want to run this once. It also doesn't matter if it runs on the next tick since it's only
|
||||
// a nice to have UI feature.
|
||||
onDidChangeActiveNodes.input = Event.chain(Event.any<any>(onDidModelSplice, this.focus.onDidChange, this.selection.onDidChange))
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
const escapeCodiconsRegex = /(?<!\\)\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
export function escapeCodicons(text: string): string {
|
||||
return text.replace(escapeCodiconsRegex, match => `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownEscapedCodiconsRegex = /\\\$\([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?\)/gi;
|
||||
export function markdownEscapeEscapedCodicons(text: string): string {
|
||||
// Need to add an extra \ for escaping in markdown
|
||||
return text.replace(markdownEscapedCodiconsRegex, match => `\\${match}`);
|
||||
}
|
||||
|
||||
const markdownUnescapeCodiconsRegex = /(?<!\\)\$\\\(([a-z0-9\-]+?(?:~[a-z0-9\-]*?)?)\\\)/gi;
|
||||
export function markdownUnescapeCodicons(text: string): string {
|
||||
return text.replace(markdownUnescapeCodiconsRegex, (_, codicon) => `$(${codicon})`);
|
||||
}
|
||||
|
||||
const renderCodiconsRegex = /(\\)?\$\((([a-z0-9\-]+?)(?:~([a-z0-9\-]*?))?)\)/gi;
|
||||
export function renderCodicons(text: string): string {
|
||||
return text.replace(renderCodiconsRegex, (_, escape, codicon, name, animation) => {
|
||||
return escape
|
||||
? `$(${codicon})`
|
||||
: `<span class="codicon codicon-${name}${animation ? ` codicon-animation-${animation}` : ''}"></span>`;
|
||||
});
|
||||
}
|
||||
@@ -84,11 +84,11 @@ export function memoize(target: any, key: string, descriptor: any) {
|
||||
return createMemoizer()(target, key, descriptor);
|
||||
}
|
||||
|
||||
export interface IDebouceReducer<T> {
|
||||
export interface IDebounceReducer<T> {
|
||||
(previousValue: T, ...args: any[]): T;
|
||||
}
|
||||
|
||||
export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initialValueProvider?: () => T): Function {
|
||||
export function debounce<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
|
||||
return createDecorator((fn, key) => {
|
||||
const timerKey = `$debounce$${key}`;
|
||||
const resultKey = `$debounce$result$${key}`;
|
||||
@@ -112,3 +112,44 @@ export function debounce<T>(delay: number, reducer?: IDebouceReducer<T>, initial
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
export function throttle<T>(delay: number, reducer?: IDebounceReducer<T>, initialValueProvider?: () => T): Function {
|
||||
return createDecorator((fn, key) => {
|
||||
const timerKey = `$throttle$timer$${key}`;
|
||||
const resultKey = `$throttle$result$${key}`;
|
||||
const lastRunKey = `$throttle$lastRun$${key}`;
|
||||
const pendingKey = `$throttle$pending$${key}`;
|
||||
|
||||
return function (this: any, ...args: any[]) {
|
||||
if (!this[resultKey]) {
|
||||
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
|
||||
}
|
||||
if (this[lastRunKey] === null || this[lastRunKey] === undefined) {
|
||||
this[lastRunKey] = -Number.MAX_VALUE;
|
||||
}
|
||||
|
||||
if (reducer) {
|
||||
this[resultKey] = reducer(this[resultKey], ...args);
|
||||
}
|
||||
|
||||
if (this[pendingKey]) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextTime = this[lastRunKey] + delay;
|
||||
if (nextTime <= Date.now()) {
|
||||
this[lastRunKey] = Date.now();
|
||||
fn.apply(this, [this[resultKey]]);
|
||||
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
|
||||
} else {
|
||||
this[pendingKey] = true;
|
||||
this[timerKey] = setTimeout(() => {
|
||||
this[pendingKey] = false;
|
||||
this[lastRunKey] = Date.now();
|
||||
fn.apply(this, [this[resultKey]]);
|
||||
this[resultKey] = initialValueProvider ? initialValueProvider() : undefined;
|
||||
}, nextTime - Date.now());
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,15 +8,11 @@ import * as types from 'vs/base/common/types';
|
||||
import * as arrays from 'vs/base/common/arrays';
|
||||
|
||||
function exceptionToErrorMessage(exception: any, verbose: boolean): string {
|
||||
if (exception.message) {
|
||||
if (verbose && (exception.stack || exception.stacktrace)) {
|
||||
return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace));
|
||||
}
|
||||
|
||||
return detectSystemErrorMessage(exception);
|
||||
if (verbose && (exception.stack || exception.stacktrace)) {
|
||||
return nls.localize('stackTrace.format', "{0}: {1}", detectSystemErrorMessage(exception), stackToString(exception.stack) || stackToString(exception.stacktrace));
|
||||
}
|
||||
|
||||
return nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
|
||||
return detectSystemErrorMessage(exception);
|
||||
}
|
||||
|
||||
function stackToString(stack: string[] | string | undefined): string | undefined {
|
||||
@@ -34,7 +30,7 @@ function detectSystemErrorMessage(exception: any): string {
|
||||
return nls.localize('nodeExceptionMessage', "A system error occurred ({0})", exception.message);
|
||||
}
|
||||
|
||||
return exception.message;
|
||||
return exception.message || nls.localize('error.defaultMessage', "An unknown error occurred. Please consult the log for more details.");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -31,7 +31,7 @@ export class ErrorHandler {
|
||||
};
|
||||
}
|
||||
|
||||
public addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
|
||||
addListener(listener: ErrorListenerCallback): ErrorListenerUnbind {
|
||||
this.listeners.push(listener);
|
||||
|
||||
return () => {
|
||||
@@ -49,21 +49,21 @@ export class ErrorHandler {
|
||||
this.listeners.splice(this.listeners.indexOf(listener), 1);
|
||||
}
|
||||
|
||||
public setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
|
||||
setUnexpectedErrorHandler(newUnexpectedErrorHandler: (e: any) => void): void {
|
||||
this.unexpectedErrorHandler = newUnexpectedErrorHandler;
|
||||
}
|
||||
|
||||
public getUnexpectedErrorHandler(): (e: any) => void {
|
||||
getUnexpectedErrorHandler(): (e: any) => void {
|
||||
return this.unexpectedErrorHandler;
|
||||
}
|
||||
|
||||
public onUnexpectedError(e: any): void {
|
||||
onUnexpectedError(e: any): void {
|
||||
this.unexpectedErrorHandler(e);
|
||||
this.emit(e);
|
||||
}
|
||||
|
||||
// For external errors, we don't want the listeners to be called
|
||||
public onUnexpectedExternalError(e: any): void {
|
||||
onUnexpectedExternalError(e: any): void {
|
||||
this.unexpectedErrorHandler(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,37 +5,51 @@
|
||||
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { UriComponents } from 'vs/base/common/uri';
|
||||
import { escapeCodicons, markdownUnescapeCodicons } from 'vs/base/common/codicons';
|
||||
|
||||
export interface IMarkdownString {
|
||||
readonly value: string;
|
||||
readonly isTrusted?: boolean;
|
||||
readonly supportThemeIcons?: boolean;
|
||||
uris?: { [href: string]: UriComponents };
|
||||
}
|
||||
|
||||
export class MarkdownString implements IMarkdownString {
|
||||
private readonly _isTrusted: boolean;
|
||||
private readonly _supportThemeIcons: boolean;
|
||||
|
||||
private _value: string;
|
||||
private _isTrusted: boolean;
|
||||
constructor(
|
||||
private _value: string = '',
|
||||
isTrustedOrOptions: boolean | { isTrusted?: boolean, supportThemeIcons?: boolean } = false,
|
||||
) {
|
||||
if (typeof isTrustedOrOptions === 'boolean') {
|
||||
this._isTrusted = isTrustedOrOptions;
|
||||
this._supportThemeIcons = false;
|
||||
}
|
||||
else {
|
||||
this._isTrusted = isTrustedOrOptions.isTrusted ?? false;
|
||||
this._supportThemeIcons = isTrustedOrOptions.supportThemeIcons ?? false;
|
||||
}
|
||||
|
||||
constructor(value: string = '', isTrusted = false) {
|
||||
this._value = value;
|
||||
this._isTrusted = isTrusted;
|
||||
}
|
||||
|
||||
get value() { return this._value; }
|
||||
get isTrusted() { return this._isTrusted; }
|
||||
get supportThemeIcons() { return this._supportThemeIcons; }
|
||||
|
||||
appendText(value: string): MarkdownString {
|
||||
// escape markdown syntax tokens: http://daringfireball.net/projects/markdown/syntax#backslash
|
||||
this._value += value
|
||||
value = value
|
||||
.replace(/[\\`*_{}[\]()#+\-.!]/g, '\\$&')
|
||||
.replace('\n', '\n\n');
|
||||
this._value += this.supportThemeIcons ? markdownUnescapeCodicons(value) : value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
appendMarkdown(value: string): MarkdownString {
|
||||
this._value += value;
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
@@ -47,6 +61,10 @@ export class MarkdownString implements IMarkdownString {
|
||||
this._value += '\n```\n';
|
||||
return this;
|
||||
}
|
||||
|
||||
static escapeThemeIcons(value: string): string {
|
||||
return escapeCodicons(value);
|
||||
}
|
||||
}
|
||||
|
||||
export function isEmptyMarkdownString(oneOrMany: IMarkdownString | IMarkdownString[] | null | undefined): boolean {
|
||||
@@ -64,7 +82,8 @@ export function isMarkdownString(thing: any): thing is IMarkdownString {
|
||||
return true;
|
||||
} else if (thing && typeof thing === 'object') {
|
||||
return typeof (<IMarkdownString>thing).value === 'string'
|
||||
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined);
|
||||
&& (typeof (<IMarkdownString>thing).isTrusted === 'boolean' || (<IMarkdownString>thing).isTrusted === undefined)
|
||||
&& (typeof (<IMarkdownString>thing).supportThemeIcons === 'boolean' || (<IMarkdownString>thing).supportThemeIcons === undefined);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -89,7 +108,7 @@ function markdownStringEqual(a: IMarkdownString, b: IMarkdownString): boolean {
|
||||
} else if (!a || !b) {
|
||||
return false;
|
||||
} else {
|
||||
return a.value === b.value && a.isTrusted === b.isTrusted;
|
||||
return a.value === b.value && a.isTrusted === b.isTrusted && a.supportThemeIcons === b.supportThemeIcons;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -227,7 +227,7 @@ export class Client implements IChannelClient, IDisposable {
|
||||
this.child.on('error', err => console.warn('IPC "' + this.options.serverName + '" errored with ' + err));
|
||||
|
||||
this.child.on('exit', (code: any, signal: any) => {
|
||||
process.removeListener('exit', onExit);
|
||||
process.removeListener('exit' as 'loaded', onExit); // https://github.com/electron/electron/issues/21475
|
||||
|
||||
this.activeRequests.forEach(r => dispose(r));
|
||||
this.activeRequests.clear();
|
||||
|
||||
@@ -6,42 +6,104 @@
|
||||
import * as assert from 'assert';
|
||||
import * as marked from 'vs/base/common/marked/marked';
|
||||
import { renderMarkdown } from 'vs/base/browser/markdownRenderer';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
suite('MarkdownRenderer', () => {
|
||||
test('image rendering conforms to default', () => {
|
||||
const markdown = { value: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
suite('Images', () => {
|
||||
|
||||
test('image rendering conforms to default', () => {
|
||||
const markdown = { value: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
});
|
||||
|
||||
test('image rendering conforms to default without title', () => {
|
||||
const markdown = { value: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
});
|
||||
|
||||
test('image width from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
|
||||
});
|
||||
|
||||
test('image height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
|
||||
});
|
||||
|
||||
test('image width and height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('image rendering conforms to default without title', () => {
|
||||
const markdown = { value: `` };
|
||||
const result: HTMLElement = renderMarkdown(markdown);
|
||||
const renderer = new marked.Renderer();
|
||||
const imageFromMarked = marked(markdown.value, {
|
||||
sanitize: true,
|
||||
renderer
|
||||
}).trim();
|
||||
assert.strictEqual(result.innerHTML, imageFromMarked);
|
||||
suite('ThemeIcons Support On', () => {
|
||||
|
||||
test('render appendText', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendText('$(zap) $(dont match me)');
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(dont match me)</p>`);
|
||||
});
|
||||
|
||||
test('render appendText escaped', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendText(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)'));
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
|
||||
});
|
||||
|
||||
test('render appendMarkdown', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendMarkdown('$(zap) $(dont match me)');
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p><span class="codicon codicon-zap"></span> $(dont match me)</p>`);
|
||||
});
|
||||
|
||||
test('render appendMarkdown escaped', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap) $(dont match me)'));
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('image width from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100"></p>`);
|
||||
suite('ThemeIcons Support Off', () => {
|
||||
|
||||
test('render appendText', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
|
||||
mds.appendText('$(zap) $(dont match me)');
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
|
||||
});
|
||||
|
||||
test('render appendMarkdown', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
|
||||
mds.appendMarkdown('$(zap) $(dont match me)');
|
||||
|
||||
let result: HTMLElement = renderMarkdown(mds);
|
||||
assert.strictEqual(result.innerHTML, `<p>$(zap) $(dont match me)</p>`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
test('image height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" height="100"></p>`);
|
||||
});
|
||||
|
||||
test('image width and height from title params', () => {
|
||||
let result: HTMLElement = renderMarkdown({ value: `` });
|
||||
assert.strictEqual(result.innerHTML, `<p><img src="someimageurl" alt="image" title="caption" width="100" height="200"></p>`);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as sinon from 'sinon';
|
||||
import * as assert from 'assert';
|
||||
import { memoize, createMemoizer } from 'vs/base/common/decorators';
|
||||
import { memoize, createMemoizer, throttle } from 'vs/base/common/decorators';
|
||||
|
||||
suite('Decorators', () => {
|
||||
test('memoize should memoize methods', () => {
|
||||
@@ -100,7 +101,9 @@ suite('Decorators', () => {
|
||||
test('memoized property should not be enumerable', () => {
|
||||
class Foo {
|
||||
@memoize
|
||||
get answer() { return 42; }
|
||||
get answer() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
const foo = new Foo();
|
||||
@@ -112,7 +115,9 @@ suite('Decorators', () => {
|
||||
test('memoized property should not be writable', () => {
|
||||
class Foo {
|
||||
@memoize
|
||||
get answer() { return 42; }
|
||||
get answer() {
|
||||
return 42;
|
||||
}
|
||||
}
|
||||
|
||||
const foo = new Foo();
|
||||
@@ -131,7 +136,9 @@ suite('Decorators', () => {
|
||||
let counter = 0;
|
||||
class Foo {
|
||||
@memoizer
|
||||
get answer() { return ++counter; }
|
||||
get answer() {
|
||||
return ++counter;
|
||||
}
|
||||
}
|
||||
|
||||
const foo = new Foo();
|
||||
@@ -145,4 +152,49 @@ suite('Decorators', () => {
|
||||
assert.equal(foo.answer, 3);
|
||||
assert.equal(foo.answer, 3);
|
||||
});
|
||||
|
||||
test('throttle', () => {
|
||||
const spy = sinon.spy();
|
||||
const clock = sinon.useFakeTimers();
|
||||
try {
|
||||
class ThrottleTest {
|
||||
private _handle: Function;
|
||||
|
||||
constructor(fn: Function) {
|
||||
this._handle = fn;
|
||||
}
|
||||
|
||||
@throttle(
|
||||
100,
|
||||
(a: number, b: number) => a + b,
|
||||
() => 0
|
||||
)
|
||||
report(p: number): void {
|
||||
this._handle(p);
|
||||
}
|
||||
}
|
||||
|
||||
const t = new ThrottleTest(spy);
|
||||
|
||||
t.report(1);
|
||||
t.report(2);
|
||||
t.report(3);
|
||||
assert.deepEqual(spy.args, [[1]]);
|
||||
|
||||
clock.tick(200);
|
||||
assert.deepEqual(spy.args, [[1], [5]]);
|
||||
spy.reset();
|
||||
|
||||
t.report(4);
|
||||
t.report(5);
|
||||
clock.tick(50);
|
||||
t.report(6);
|
||||
|
||||
assert.deepEqual(spy.args, [[4]]);
|
||||
clock.tick(60);
|
||||
assert.deepEqual(spy.args, [[4], [11]]);
|
||||
} finally {
|
||||
clock.restore();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||
|
||||
@@ -16,9 +17,17 @@ suite('Errors', () => {
|
||||
error.detail.exception = {};
|
||||
error.detail.exception.message = 'Foo Bar';
|
||||
assert.strictEqual(toErrorMessage(error), 'Foo Bar');
|
||||
assert.strictEqual(toErrorMessage(error, true), 'Foo Bar');
|
||||
|
||||
assert(toErrorMessage());
|
||||
assert(toErrorMessage(null));
|
||||
assert(toErrorMessage({}));
|
||||
|
||||
try {
|
||||
throw new Error();
|
||||
} catch (error) {
|
||||
assert.strictEqual(toErrorMessage(error), 'An unknown error occurred. Please consult the log for more details.');
|
||||
assert.ok(toErrorMessage(error, true).length > 'An unknown error occurred. Please consult the log for more details.'.length);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -43,4 +43,14 @@ suite('Hash', () => {
|
||||
assert.notEqual(hash({ 'foo': 'bar' }), hash({ 'foo': 'bar2' }));
|
||||
assert.notEqual(hash({}), hash([]));
|
||||
});
|
||||
});
|
||||
|
||||
test('array - unexpected collision', function () {
|
||||
this.skip();
|
||||
const a = hash([undefined, undefined, undefined, undefined, undefined]);
|
||||
const b = hash([undefined, undefined, 'HHHHHH', [{ line: 0, character: 0 }, { line: 0, character: 0 }], undefined]);
|
||||
// console.log(a);
|
||||
// console.log(b);
|
||||
assert.notEqual(a, b);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import * as assert from 'assert';
|
||||
import { MarkdownString } from 'vs/base/common/htmlContent';
|
||||
|
||||
suite('markdownString', () => {
|
||||
suite('MarkdownString', () => {
|
||||
|
||||
test('escape', () => {
|
||||
|
||||
@@ -16,4 +16,63 @@ suite('markdownString', () => {
|
||||
|
||||
assert.equal(mds.value, '\\# foo\n\n\\*bar\\*');
|
||||
});
|
||||
|
||||
suite('ThemeIcons', () => {
|
||||
|
||||
test('escapeThemeIcons', () => {
|
||||
assert.equal(
|
||||
MarkdownString.escapeThemeIcons('$(zap) $(not an icon) foo$(bar)'),
|
||||
'\\$(zap) $(not an icon) foo\\$(bar)'
|
||||
);
|
||||
});
|
||||
|
||||
suite('Support On', () => {
|
||||
|
||||
test('appendText', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendText('$(zap)');
|
||||
|
||||
assert.equal(mds.value, '$(zap)');
|
||||
});
|
||||
|
||||
test('appendText escaped', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendText(MarkdownString.escapeThemeIcons('$(zap)'));
|
||||
|
||||
assert.equal(mds.value, '\\\\$\\(zap\\)');
|
||||
});
|
||||
|
||||
test('appendMarkdown', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendMarkdown('$(zap)');
|
||||
|
||||
assert.equal(mds.value, '$(zap)');
|
||||
});
|
||||
|
||||
test('appendMarkdown escaped', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: true });
|
||||
mds.appendMarkdown(MarkdownString.escapeThemeIcons('$(zap)'));
|
||||
|
||||
assert.equal(mds.value, '\\$(zap)');
|
||||
});
|
||||
});
|
||||
|
||||
suite('Support Off', () => {
|
||||
|
||||
test('appendText', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
|
||||
mds.appendText('$(zap)');
|
||||
|
||||
assert.equal(mds.value, '$\\(zap\\)');
|
||||
});
|
||||
|
||||
test('appendMarkdown', () => {
|
||||
const mds = new MarkdownString(undefined, { supportThemeIcons: false });
|
||||
mds.appendMarkdown('$(zap)');
|
||||
|
||||
assert.equal(mds.value, '$(zap)');
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,43 +3,43 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/issueReporter';
|
||||
import { shell, ipcRenderer, webFrame, clipboard } from 'electron';
|
||||
import { localize } from 'vs/nls';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import * as collections from 'vs/base/common/collections';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { clipboard, ipcRenderer, shell, webFrame } from 'electron';
|
||||
import * as os from 'os';
|
||||
import * as browser from 'vs/base/browser/browser';
|
||||
import { $ } from 'vs/base/browser/dom';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
|
||||
import * as collections from 'vs/base/common/collections';
|
||||
import { debounce } from 'vs/base/common/decorators';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import * as platform from 'vs/base/common/platform';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import { getDelayedChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { createChannelSender } from 'vs/base/parts/ipc/node/ipc';
|
||||
import { connect as connectNet } from 'vs/base/parts/ipc/node/ipc.net';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
|
||||
import { NullTelemetryService, combinedAppender, LogAppender } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { MainProcessService, IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { IssueReporterModel, IssueReporterData as IssueReporterModelData } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import { IssueReporterData, IssueReporterStyles, IssueType, ISettingsSearchIssueReporterData, IssueReporterFeatures, IssueReporterExtensionData } from 'vs/platform/issue/node/issue';
|
||||
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
|
||||
import { LoggerChannelClient, FollowerLogService } from 'vs/platform/log/common/logIpc';
|
||||
import { ILogService, getLogLevel } from 'vs/platform/log/common/log';
|
||||
import { CodiconLabel } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
|
||||
import { normalizeGitHubUrl } from 'vs/code/common/issue/issueReporterUtil';
|
||||
import { Button } from 'vs/base/browser/ui/button/button';
|
||||
import { SystemInfo, isRemoteDiagnosticError } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import { IssueReporterData as IssueReporterModelData, IssueReporterModel } from 'vs/code/electron-browser/issue/issueReporterModel';
|
||||
import BaseHtml from 'vs/code/electron-browser/issue/issueReporterPage';
|
||||
import 'vs/css!./media/issueReporter';
|
||||
import { localize } from 'vs/nls';
|
||||
import { isRemoteDiagnosticError, SystemInfo } from 'vs/platform/diagnostics/common/diagnostics';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { EnvironmentService } from 'vs/platform/environment/node/environmentService';
|
||||
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IMainProcessService, MainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { ISharedProcessService } from 'vs/platform/ipc/electron-browser/sharedProcessService';
|
||||
import { ISettingsSearchIssueReporterData, IssueReporterData, IssueReporterExtensionData, IssueReporterFeatures, IssueReporterStyles, IssueType } from 'vs/platform/issue/node/issue';
|
||||
import { getLogLevel, ILogService } from 'vs/platform/log/common/log';
|
||||
import { FollowerLogService, LoggerChannelClient } from 'vs/platform/log/common/logIpc';
|
||||
import { SpdLogService } from 'vs/platform/log/node/spdlogService';
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { ITelemetryServiceConfig, TelemetryService } from 'vs/platform/telemetry/common/telemetryService';
|
||||
import { combinedAppender, LogAppender, NullTelemetryService } from 'vs/platform/telemetry/common/telemetryUtils';
|
||||
import { resolveCommonProperties } from 'vs/platform/telemetry/node/commonProperties';
|
||||
import { TelemetryAppenderClient } from 'vs/platform/telemetry/node/telemetryIpc';
|
||||
import { IWindowConfiguration } from 'vs/platform/windows/common/windows';
|
||||
|
||||
const MAX_URL_LENGTH = 2045;
|
||||
|
||||
@@ -226,7 +226,7 @@ export class IssueReporter extends Disposable {
|
||||
}
|
||||
|
||||
if (styles.buttonHoverBackground) {
|
||||
content.push(`.monaco-text-button:hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`);
|
||||
content.push(`.monaco-text-button:not(.disabled):hover, .monaco-text-button:focus { background-color: ${styles.buttonHoverBackground} !important; }`);
|
||||
}
|
||||
|
||||
styleTag.innerHTML = content.join('\n');
|
||||
@@ -432,6 +432,11 @@ export class IssueReporter extends Disposable {
|
||||
sendWorkbenchCommand('workbench.action.reloadWindowWithExtensionsDisabled');
|
||||
});
|
||||
|
||||
this.addEventListener('extensionBugsLink', 'click', (e: MouseEvent) => {
|
||||
const url = (<HTMLElement>e.target).innerText;
|
||||
shell.openExternal(url);
|
||||
});
|
||||
|
||||
this.addEventListener('disableExtensions', 'keydown', (e: Event) => {
|
||||
e.stopPropagation();
|
||||
if ((e as KeyboardEvent).keyCode === 13 || (e as KeyboardEvent).keyCode === 32) {
|
||||
@@ -1029,15 +1034,63 @@ export class IssueReporter extends Disposable {
|
||||
const matches = extensions.filter(extension => extension.id === selectedExtensionId);
|
||||
if (matches.length) {
|
||||
this.issueReporterModel.update({ selectedExtension: matches[0] });
|
||||
this.validateSelectedExtension();
|
||||
|
||||
const title = (<HTMLInputElement>this.getElementById('issue-title')).value;
|
||||
this.searchExtensionIssues(title);
|
||||
} else {
|
||||
this.issueReporterModel.update({ selectedExtension: undefined });
|
||||
this.clearSearchResults();
|
||||
this.validateSelectedExtension();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.addEventListener('problem-source', 'change', (_) => {
|
||||
this.validateSelectedExtension();
|
||||
});
|
||||
}
|
||||
|
||||
private validateSelectedExtension(): void {
|
||||
const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!;
|
||||
const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!;
|
||||
hide(extensionValidationMessage);
|
||||
hide(extensionValidationNoUrlsMessage);
|
||||
|
||||
if (!this.issueReporterModel.getData().selectedExtension) {
|
||||
this.previewButton.enabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const hasValidGitHubUrl = this.getExtensionGitHubUrl();
|
||||
if (hasValidGitHubUrl) {
|
||||
this.previewButton.enabled = true;
|
||||
} else {
|
||||
this.setExtensionValidationMessage();
|
||||
this.previewButton.enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
private setExtensionValidationMessage(): void {
|
||||
const extensionValidationMessage = this.getElementById('extension-selection-validation-error')!;
|
||||
const extensionValidationNoUrlsMessage = this.getElementById('extension-selection-validation-error-no-url')!;
|
||||
const bugsUrl = this.getExtensionBugsUrl();
|
||||
if (bugsUrl) {
|
||||
show(extensionValidationMessage);
|
||||
const link = this.getElementById('extensionBugsLink')!;
|
||||
link.textContent = bugsUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
const extensionUrl = this.getExtensionRepositoryUrl();
|
||||
if (extensionUrl) {
|
||||
show(extensionValidationMessage);
|
||||
const link = this.getElementById('extensionBugsLink');
|
||||
link!.textContent = extensionUrl;
|
||||
return;
|
||||
}
|
||||
|
||||
show(extensionValidationNoUrlsMessage);
|
||||
}
|
||||
|
||||
private updateProcessInfo(state: IssueReporterModelData) {
|
||||
|
||||
@@ -32,6 +32,11 @@ export default (): string => `
|
||||
<select id="extension-selector" class="inline-form-control">
|
||||
<!-- To be dynamically filled -->
|
||||
</select>
|
||||
<div id="extension-selection-validation-error" class="validation-error hidden" role="alert">${escape(localize('extensionWithNonstandardBugsUrl', "The issue reporter is unable to create issues for this extension. Please visit {0} to report an issue."))
|
||||
.replace('{0}', `<span tabIndex=0 role="button" id="extensionBugsLink" class="workbenchCommand"><!-- To be dynamically filled --></span>`)}</div>
|
||||
<div id="extension-selection-validation-error-no-url" class="validation-error hidden" role="alert">
|
||||
${escape(localize('extensionWithNoBugsUrl', "The issue reporter is unable to create issues for this extension, as it does not specify a URL for reporting issues. Please check the marketplace page of this extension so see if other instructions are available."))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -50,11 +50,10 @@ import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, ISettingsMergeService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
|
||||
import { UserDataSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { SettingsMergeChannelClient } from 'vs/platform/userDataSync/common/settingsSyncIpc';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { IElectronService } from 'vs/platform/electron/node/electron';
|
||||
import { LoggerService } from 'vs/platform/log/node/loggerService';
|
||||
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog';
|
||||
@@ -63,7 +62,6 @@ import { AuthTokenService } from 'vs/platform/auth/electron-browser/authTokenSer
|
||||
import { AuthTokenChannel } from 'vs/platform/auth/common/authTokenIpc';
|
||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
|
||||
import { UserDataSyncUtilServiceClient } from 'vs/platform/userDataSync/common/keybindingsSyncIpc';
|
||||
import { UserDataAutoSync } from 'vs/platform/userDataSync/electron-browser/userDataAutoSync';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
@@ -186,8 +184,6 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
|
||||
services.set(IAuthTokenService, new SyncDescriptor(AuthTokenService));
|
||||
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
|
||||
const settingsMergeChannel = server.getChannel('settingsMerge', activeWindowRouter);
|
||||
services.set(ISettingsMergeService, new SettingsMergeChannelClient(settingsMergeChannel));
|
||||
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', activeWindowRouter)));
|
||||
services.set(IUserDataSyncStoreService, new SyncDescriptor(UserDataSyncStoreService));
|
||||
services.set(IUserDataSyncService, new SyncDescriptor(UserDataSyncService));
|
||||
|
||||
@@ -140,8 +140,13 @@ export class CodeWindow extends Disposable implements ICodeWindow {
|
||||
}
|
||||
};
|
||||
|
||||
// Apply icon to window
|
||||
// Linux: always
|
||||
// Windows: only when running out of sources, otherwise an icon is set by us on the executable
|
||||
if (isLinux) {
|
||||
options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png'); // Windows and Mac are better off using the embedded icon(s)
|
||||
options.icon = path.join(this.environmentService.appRoot, 'resources/linux/code.png');
|
||||
} else if (isWindows && !this.environmentService.isBuilt) {
|
||||
options.icon = path.join(this.environmentService.appRoot, 'resources/win32/code_150x150.png');
|
||||
}
|
||||
|
||||
const windowConfig = this.configurationService.getValue<IWindowSettings>('window');
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IConstructorSignature1, ServicesAccessor as InstantiationServicesAccess
|
||||
import { IKeybindings, KeybindingsRegistry } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { withNullAsUndefined, assertType } from 'vs/base/common/types';
|
||||
|
||||
export type ServicesAccessor = InstantiationServicesAccessor;
|
||||
export type IEditorContributionCtor = IConstructorSignature1<ICodeEditor, IEditorContribution>;
|
||||
@@ -311,6 +311,60 @@ export function registerDefaultLanguageCommand(id: string, handler: (model: ITex
|
||||
});
|
||||
}
|
||||
|
||||
export function registerModelAndPositionCommand(id: string, handler: (model: ITextModel, position: Position, ...args: any[]) => any) {
|
||||
CommandsRegistry.registerCommand(id, function (accessor, ...args) {
|
||||
|
||||
const [resource, position] = args;
|
||||
assertType(URI.isUri(resource));
|
||||
assertType(Position.isIPosition(position));
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
if (model) {
|
||||
const editorPosition = Position.lift(position);
|
||||
return handler(model, editorPosition, args.slice(2));
|
||||
}
|
||||
|
||||
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = handler(reference.object.textEditorModel, Position.lift(position), args.slice(2));
|
||||
resolve(result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}).finally(() => {
|
||||
reference.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function registerModelCommand(id: string, handler: (model: ITextModel, ...args: any[]) => any) {
|
||||
CommandsRegistry.registerCommand(id, function (accessor, ...args) {
|
||||
|
||||
const [resource] = args;
|
||||
assertType(URI.isUri(resource));
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
if (model) {
|
||||
return handler(model, args.slice(1));
|
||||
}
|
||||
|
||||
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = handler(reference.object.textEditorModel, args.slice(1));
|
||||
resolve(result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}).finally(() => {
|
||||
reference.dispose();
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export function registerEditorCommand<T extends EditorCommand>(editorCommand: T): T {
|
||||
EditorContributionRegistry.INSTANCE.registerEditorCommand(editorCommand);
|
||||
return editorCommand;
|
||||
|
||||
@@ -50,6 +50,7 @@ export class EditorScrollbar extends ViewPart {
|
||||
horizontalScrollbarSize: scrollbar.horizontalScrollbarSize,
|
||||
horizontalSliderSize: scrollbar.horizontalSliderSize,
|
||||
handleMouseWheel: scrollbar.handleMouseWheel,
|
||||
alwaysConsumeMouseWheel: scrollbar.alwaysConsumeMouseWheel,
|
||||
arrowSize: scrollbar.arrowSize,
|
||||
mouseWheelScrollSensitivity: mouseWheelScrollSensitivity,
|
||||
fastScrollSensitivity: fastScrollSensitivity,
|
||||
|
||||
@@ -417,9 +417,12 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
|
||||
// Current model is the new model
|
||||
return;
|
||||
}
|
||||
|
||||
const hasTextFocus = this.hasTextFocus();
|
||||
const detachedModel = this._detachModel();
|
||||
this._attachModel(model);
|
||||
if (hasTextFocus && this.hasModel()) {
|
||||
this.focus();
|
||||
}
|
||||
|
||||
const e: editorCommon.IModelChangedEvent = {
|
||||
oldModelUrl: detachedModel ? detachedModel.uri : null,
|
||||
|
||||
@@ -2327,6 +2327,11 @@ export interface IEditorScrollbarOptions {
|
||||
* Defaults to true.
|
||||
*/
|
||||
handleMouseWheel?: boolean;
|
||||
/**
|
||||
* Always consume mouse wheel events (always call preventDefault() and stopPropagation() on the browser events).
|
||||
* Defaults to true.
|
||||
*/
|
||||
alwaysConsumeMouseWheel?: boolean;
|
||||
/**
|
||||
* Height in pixels for the horizontal scrollbar.
|
||||
* Defaults to 10 (px).
|
||||
@@ -2357,6 +2362,7 @@ export interface InternalEditorScrollbarOptions {
|
||||
readonly verticalHasArrows: boolean;
|
||||
readonly horizontalHasArrows: boolean;
|
||||
readonly handleMouseWheel: boolean;
|
||||
readonly alwaysConsumeMouseWheel: boolean;
|
||||
readonly horizontalScrollbarSize: number;
|
||||
readonly horizontalSliderSize: number;
|
||||
readonly verticalScrollbarSize: number;
|
||||
@@ -2391,6 +2397,7 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
|
||||
verticalScrollbarSize: 14,
|
||||
verticalSliderSize: 14,
|
||||
handleMouseWheel: true,
|
||||
alwaysConsumeMouseWheel: true
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -2410,6 +2417,7 @@ class EditorScrollbar extends BaseEditorOption<EditorOption.scrollbar, InternalE
|
||||
verticalHasArrows: EditorBooleanOption.boolean(input.verticalHasArrows, this.defaultValue.verticalHasArrows),
|
||||
horizontalHasArrows: EditorBooleanOption.boolean(input.horizontalHasArrows, this.defaultValue.horizontalHasArrows),
|
||||
handleMouseWheel: EditorBooleanOption.boolean(input.handleMouseWheel, this.defaultValue.handleMouseWheel),
|
||||
alwaysConsumeMouseWheel: EditorBooleanOption.boolean(input.alwaysConsumeMouseWheel, this.defaultValue.alwaysConsumeMouseWheel),
|
||||
horizontalScrollbarSize: horizontalScrollbarSize,
|
||||
horizontalSliderSize: EditorIntOption.clampedInt(input.horizontalSliderSize, horizontalScrollbarSize, 0, 1000),
|
||||
verticalScrollbarSize: verticalScrollbarSize,
|
||||
@@ -3358,7 +3366,7 @@ export const EditorOptions = {
|
||||
lineNumbers: register(new EditorRenderLineNumbersOption()),
|
||||
lineNumbersMinChars: register(new EditorIntOption(
|
||||
EditorOption.lineNumbersMinChars, 'lineNumbersMinChars',
|
||||
5, 1, 10
|
||||
5, 1, 300
|
||||
)),
|
||||
links: register(new EditorBooleanOption(
|
||||
EditorOption.links, 'links', true,
|
||||
|
||||
@@ -386,7 +386,7 @@ export class TextModelResolvedOptions {
|
||||
defaultEOL: DefaultEndOfLine;
|
||||
trimAutoWhitespace: boolean;
|
||||
}) {
|
||||
this.tabSize = src.tabSize | 0;
|
||||
this.tabSize = Math.max(1, src.tabSize | 0);
|
||||
this.indentSize = src.tabSize | 0;
|
||||
this.insertSpaces = Boolean(src.insertSpaces);
|
||||
this.defaultEOL = src.defaultEOL | 0;
|
||||
|
||||
@@ -1246,9 +1246,9 @@ export function isResourceTextEdit(thing: any): thing is ResourceTextEdit {
|
||||
}
|
||||
|
||||
export interface ResourceFileEdit {
|
||||
oldUri: URI;
|
||||
newUri: URI;
|
||||
options: { overwrite?: boolean, ignoreIfNotExists?: boolean, ignoreIfExists?: boolean, recursive?: boolean };
|
||||
oldUri?: URI;
|
||||
newUri?: URI;
|
||||
options?: { overwrite?: boolean, ignoreIfNotExists?: boolean, ignoreIfExists?: boolean, recursive?: boolean };
|
||||
}
|
||||
|
||||
export interface ResourceTextEdit {
|
||||
|
||||
@@ -5,7 +5,8 @@
|
||||
|
||||
import 'vs/css!./codelensWidget';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { renderCodicons } from 'vs/base/browser/ui/codiconLabel/codiconLabel';
|
||||
import { renderCodicons } from 'vs/base/common/codicons';
|
||||
import { escape } from 'vs/base/common/strings';
|
||||
import * as editorBrowser from 'vs/editor/browser/editorBrowser';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { IModelDecorationsChangeAccessor, IModelDeltaDecoration, ITextModel } from 'vs/editor/common/model';
|
||||
@@ -88,7 +89,7 @@ class CodeLensContentWidget implements editorBrowser.IContentWidget {
|
||||
}
|
||||
hasSymbol = true;
|
||||
if (lens.command) {
|
||||
const title = renderCodicons(lens.command.title);
|
||||
const title = renderCodicons(escape(lens.command.title));
|
||||
if (lens.command.id) {
|
||||
innerHtml += `<a id=${i}>${title}</a>`;
|
||||
this._commands.set(String(i), lens.command);
|
||||
|
||||
@@ -10,7 +10,7 @@ import { illegalArgument, onUnexpectedExternalError } from 'vs/base/common/error
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { CodeEditorStateFlag, EditorStateCancellationTokenSource, TextModelCancellationTokenSource } from 'vs/editor/browser/core/editorState';
|
||||
import { IActiveCodeEditor, isCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { registerLanguageCommand, ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { ServicesAccessor } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
@@ -25,6 +25,8 @@ import { ExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { LinkedList } from 'vs/base/common/linkedList';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
|
||||
export function alertFormattingEdits(edits: ISingleEditOperation[]): void {
|
||||
|
||||
@@ -354,11 +356,11 @@ export function getOnTypeFormattingEdits(
|
||||
});
|
||||
}
|
||||
|
||||
registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args) {
|
||||
const { resource, range, options } = args;
|
||||
if (!(resource instanceof URI) || !Range.isIRange(range)) {
|
||||
throw illegalArgument();
|
||||
}
|
||||
CommandsRegistry.registerCommand('_executeFormatRangeProvider', function (accessor, ...args) {
|
||||
const [resource, range, options] = args;
|
||||
assertType(URI.isUri(resource));
|
||||
assertType(Range.isIRange(range));
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
if (!model) {
|
||||
throw illegalArgument('resource');
|
||||
@@ -366,11 +368,10 @@ registerLanguageCommand('_executeFormatRangeProvider', function (accessor, args)
|
||||
return getDocumentRangeFormattingEditsUntilResult(accessor.get(IEditorWorkerService), model, Range.lift(range), options, CancellationToken.None);
|
||||
});
|
||||
|
||||
registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, args) {
|
||||
const { resource, options } = args;
|
||||
if (!(resource instanceof URI)) {
|
||||
throw illegalArgument('resource');
|
||||
}
|
||||
CommandsRegistry.registerCommand('_executeFormatDocumentProvider', function (accessor, ...args) {
|
||||
const [resource, options] = args;
|
||||
assertType(URI.isUri(resource));
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
if (!model) {
|
||||
throw illegalArgument('resource');
|
||||
@@ -379,11 +380,12 @@ registerLanguageCommand('_executeFormatDocumentProvider', function (accessor, ar
|
||||
return getDocumentFormattingEditsUntilResult(accessor.get(IEditorWorkerService), model, options, CancellationToken.None);
|
||||
});
|
||||
|
||||
registerLanguageCommand('_executeFormatOnTypeProvider', function (accessor, args) {
|
||||
const { resource, position, ch, options } = args;
|
||||
if (!(resource instanceof URI) || !Position.isIPosition(position) || typeof ch !== 'string') {
|
||||
throw illegalArgument();
|
||||
}
|
||||
CommandsRegistry.registerCommand('_executeFormatOnTypeProvider', function (accessor, ...args) {
|
||||
const [resource, position, ch, options] = args;
|
||||
assertType(URI.isUri(resource));
|
||||
assertType(Position.isIPosition(position));
|
||||
assertType(typeof ch === 'string');
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
if (!model) {
|
||||
throw illegalArgument('resource');
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { flatten, coalesce } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { LocationLink, DefinitionProviderRegistry, ImplementationProviderRegistry, TypeDefinitionProviderRegistry, DeclarationProviderRegistry, ProviderResult, ReferenceProviderRegistry } from 'vs/editor/common/modes';
|
||||
@@ -72,8 +72,8 @@ export function getReferencesAtPosition(model: ITextModel, position: Position, c
|
||||
});
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None));
|
||||
registerDefaultLanguageCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeDefinitionProvider', (model, position) => getDefinitionsAtPosition(model, position, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeDeclarationProvider', (model, position) => getDeclarationsAtPosition(model, position, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeImplementationProvider', (model, position) => getImplementationsAtPosition(model, position, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeTypeDefinitionProvider', (model, position) => getTypeDefinitionsAtPosition(model, position, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeReferenceProvider', (model, position) => getReferencesAtPosition(model, position, false, CancellationToken.None));
|
||||
|
||||
@@ -23,7 +23,7 @@ import { CancelablePromise, createCancelablePromise } from 'vs/base/common/async
|
||||
import { getOuterEditor, PeekContext } from 'vs/editor/contrib/peekView/peekView';
|
||||
import { IListService, WorkbenchListFocusContextKey } from 'vs/platform/list/browser/listService';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes';
|
||||
|
||||
export const ctxReferenceSearchVisible = new RawContextKey<boolean>('referenceSearchVisible', false);
|
||||
|
||||
@@ -173,6 +173,18 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
});
|
||||
}
|
||||
|
||||
changeFocusBetweenPreviewAndReferences() {
|
||||
if (!this._widget) {
|
||||
// can be called while still resolving...
|
||||
return;
|
||||
}
|
||||
if (this._widget.isPreviewEditorFocused()) {
|
||||
this._widget.focusOnReferenceTree();
|
||||
} else {
|
||||
this._widget.focusOnPreviewEditor();
|
||||
}
|
||||
}
|
||||
|
||||
async goToNextOrPreviousReference(fwd: boolean) {
|
||||
if (!this._editor.hasModel() || !this._model || !this._widget) {
|
||||
// can be called while still resolving...
|
||||
@@ -229,7 +241,7 @@ export abstract class ReferencesController implements editorCommon.IEditorContri
|
||||
if (this._editor === openedEditor) {
|
||||
//
|
||||
this._widget.show(range);
|
||||
this._widget.focus();
|
||||
this._widget.focusOnReferenceTree();
|
||||
|
||||
} else {
|
||||
// we opened a different editor instance which means a different controller instance.
|
||||
@@ -278,6 +290,18 @@ function withController(accessor: ServicesAccessor, fn: (controller: ReferencesC
|
||||
}
|
||||
}
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'changePeekFocus',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
primary: KeyChord(KeyMod.CtrlCmd | KeyCode.KEY_K, KeyCode.F2),
|
||||
when: ContextKeyExpr.or(ctxReferenceSearchVisible, PeekContext.inPeekEditor),
|
||||
handler(accessor) {
|
||||
withController(accessor, controller => {
|
||||
controller.changeFocusBetweenPreviewAndReferences();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||
id: 'goToNextReference',
|
||||
weight: KeybindingWeight.WorkbenchContrib + 50,
|
||||
|
||||
@@ -251,10 +251,18 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
|
||||
super.show(where, this.layoutData.heightInLines || 18);
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
focusOnReferenceTree(): void {
|
||||
this._tree.domFocus();
|
||||
}
|
||||
|
||||
focusOnPreviewEditor(): void {
|
||||
this._preview.focus();
|
||||
}
|
||||
|
||||
isPreviewEditorFocused(): boolean {
|
||||
return this._preview.hasTextFocus();
|
||||
}
|
||||
|
||||
protected _onTitleClick(e: IMouseEvent): void {
|
||||
if (this._preview && this._preview.getModel()) {
|
||||
this._onDidSelectReference.fire({
|
||||
@@ -283,7 +291,8 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
|
||||
horizontal: 'auto',
|
||||
useShadows: true,
|
||||
verticalHasArrows: false,
|
||||
horizontalHasArrows: false
|
||||
horizontalHasArrows: false,
|
||||
alwaysConsumeMouseWheel: false
|
||||
},
|
||||
overviewRulerLanes: 2,
|
||||
fixedOverflowWidgets: true,
|
||||
@@ -457,7 +466,7 @@ export class ReferenceWidget extends peekView.PeekViewWidget {
|
||||
dom.show(this._treeContainer);
|
||||
dom.show(this._previewContainer);
|
||||
this._splitView.layout(this._dim.width);
|
||||
this.focus();
|
||||
this.focusOnReferenceTree();
|
||||
|
||||
// pick input and a reference to begin with
|
||||
return this._tree.setInput(this._model.groups.length === 1 ? this._model.groups[0] : this._model);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { onUnexpectedExternalError } from 'vs/base/common/errors';
|
||||
import { registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { Hover, HoverProviderRegistry } from 'vs/editor/common/modes';
|
||||
@@ -27,7 +27,7 @@ export function getHover(model: ITextModel, position: Position, token: Cancellat
|
||||
return Promise.all(promises).then(coalesce);
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeHoverProvider', (model, position) => getHover(model, position, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeHoverProvider', (model, position) => getHover(model, position, CancellationToken.None));
|
||||
|
||||
function isValid(result: Hover) {
|
||||
const hasRange = (typeof result.range !== 'undefined');
|
||||
|
||||
@@ -19,7 +19,7 @@ export class ContentHoverWidget extends Widget implements IContentWidget {
|
||||
protected _editor: ICodeEditor;
|
||||
private _isVisible: boolean;
|
||||
private readonly _containerDomNode: HTMLElement;
|
||||
private readonly _domNode: HTMLElement;
|
||||
protected readonly _domNode: HTMLElement;
|
||||
protected _showAtPosition: Position | null;
|
||||
protected _showAtRange: Range | null;
|
||||
private _stoleFocus: boolean;
|
||||
|
||||
@@ -13,7 +13,7 @@ import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { IRange, Range } from 'vs/editor/common/core/range';
|
||||
import { ModelDecorationOptions } from 'vs/editor/common/model/textModel';
|
||||
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor } from 'vs/editor/common/modes';
|
||||
import { DocumentColorProvider, Hover as MarkdownHover, HoverProviderRegistry, IColor, TokenizationRegistry } from 'vs/editor/common/modes';
|
||||
import { getColorPresentations } from 'vs/editor/contrib/colorPicker/color';
|
||||
import { ColorDetector } from 'vs/editor/contrib/colorPicker/colorDetector';
|
||||
import { ColorPickerModel } from 'vs/editor/contrib/colorPicker/colorPickerModel';
|
||||
@@ -238,6 +238,12 @@ export class ModesContentHoverWidget extends ContentHoverWidget {
|
||||
this._register(editor.onDidChangeConfiguration((e) => {
|
||||
this._hoverOperation.setHoverTime(this._editor.getOption(EditorOption.hover).delay);
|
||||
}));
|
||||
this._register(TokenizationRegistry.onDidChange((e) => {
|
||||
if (this.isVisible && this._lastRange && this._messages.length > 0) {
|
||||
this._domNode.textContent = '';
|
||||
this._renderMessages(this._lastRange, this._messages);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
|
||||
@@ -176,7 +176,7 @@ class MessageWidget implements IContentWidget {
|
||||
}
|
||||
|
||||
getPosition(): IContentWidgetPosition {
|
||||
return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE] };
|
||||
return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE, ContentWidgetPositionPreference.BELOW] };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@
|
||||
white-space: initial;
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget .docs .markdown-docs p code {
|
||||
font-family: var(--monaco-monospace-font);
|
||||
}
|
||||
|
||||
.monaco-editor .parameter-hints-widget .docs .code {
|
||||
white-space: pre-wrap;
|
||||
}
|
||||
|
||||
@@ -3,17 +3,17 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { illegalArgument } from 'vs/base/common/errors';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { ITextModel } from 'vs/editor/common/model';
|
||||
import { registerLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { DocumentSymbol } from 'vs/editor/common/modes';
|
||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { ITextModelService } from 'vs/editor/common/services/resolverService';
|
||||
import { OutlineModel, OutlineElement } from 'vs/editor/contrib/documentSymbols/outlineModel';
|
||||
import { values } from 'vs/base/common/collections';
|
||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||
import { assertType } from 'vs/base/common/types';
|
||||
|
||||
export async function getDocumentSymbols(document: ITextModel, flat: boolean, token: CancellationToken): Promise<DocumentSymbol[]> {
|
||||
|
||||
@@ -63,26 +63,19 @@ function flatten(bucket: DocumentSymbol[], entries: DocumentSymbol[], overrideCo
|
||||
}
|
||||
|
||||
|
||||
registerLanguageCommand('_executeDocumentSymbolProvider', function (accessor, args) {
|
||||
const { resource } = args;
|
||||
if (!(resource instanceof URI)) {
|
||||
throw illegalArgument('resource');
|
||||
}
|
||||
CommandsRegistry.registerCommand('_executeDocumentSymbolProvider', async function (accessor, ...args) {
|
||||
const [resource] = args;
|
||||
assertType(URI.isUri(resource));
|
||||
|
||||
const model = accessor.get(IModelService).getModel(resource);
|
||||
if (model) {
|
||||
return getDocumentSymbols(model, false, CancellationToken.None);
|
||||
}
|
||||
|
||||
return accessor.get(ITextModelService).createModelReference(resource).then(reference => {
|
||||
return new Promise((resolve, reject) => {
|
||||
try {
|
||||
const result = getDocumentSymbols(reference.object.textEditorModel, false, CancellationToken.None);
|
||||
resolve(result);
|
||||
} catch (err) {
|
||||
reject(err);
|
||||
}
|
||||
}).finally(() => {
|
||||
reference.dispose();
|
||||
});
|
||||
});
|
||||
const reference = await accessor.get(ITextModelService).createModelReference(resource);
|
||||
try {
|
||||
return await getDocumentSymbols(reference.object.textEditorModel, false, CancellationToken.None);
|
||||
} finally {
|
||||
reference.dispose();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -7,7 +7,7 @@ import * as arrays from 'vs/base/common/arrays';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor, registerDefaultLanguageCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, ServicesAccessor, registerModelCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
import { Selection } from 'vs/editor/common/core/selection';
|
||||
@@ -302,6 +302,7 @@ export function provideSelectionRanges(model: ITextModel, positions: Position[],
|
||||
});
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeSelectionRangeProvider', function (model, _position, args) {
|
||||
return provideSelectionRanges(model, args.positions, CancellationToken.None);
|
||||
registerModelCommand('_executeSelectionRangeProvider', function (model, ...args) {
|
||||
const [positions] = args;
|
||||
return provideSelectionRanges(model, positions, CancellationToken.None);
|
||||
});
|
||||
|
||||
@@ -21,6 +21,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
|
||||
import { WordDistance } from 'vs/editor/contrib/suggest/wordDistance';
|
||||
import { EditorOption } from 'vs/editor/common/config/editorOptions';
|
||||
import { isLowSurrogate, isHighSurrogate } from 'vs/base/common/strings';
|
||||
|
||||
export interface ICancelEvent {
|
||||
readonly retrigger: boolean;
|
||||
@@ -95,7 +96,7 @@ export class SuggestModel implements IDisposable {
|
||||
|
||||
private readonly _toDispose = new DisposableStore();
|
||||
private _quickSuggestDelay: number = 10;
|
||||
private _triggerCharacterListener?: IDisposable;
|
||||
private readonly _triggerCharacterListener = new DisposableStore();
|
||||
private readonly _triggerQuickSuggest = new TimeoutTimer();
|
||||
private _state: State = State.Idle;
|
||||
|
||||
@@ -181,8 +182,7 @@ export class SuggestModel implements IDisposable {
|
||||
}
|
||||
|
||||
private _updateTriggerCharacters(): void {
|
||||
|
||||
dispose(this._triggerCharacterListener);
|
||||
this._triggerCharacterListener.clear();
|
||||
|
||||
if (this._editor.getOption(EditorOption.readOnly)
|
||||
|| !this._editor.hasModel()
|
||||
@@ -191,29 +191,49 @@ export class SuggestModel implements IDisposable {
|
||||
return;
|
||||
}
|
||||
|
||||
const supportsByTriggerCharacter: { [ch: string]: Set<CompletionItemProvider> } = Object.create(null);
|
||||
const supportsByTriggerCharacter = new Map<string, Set<CompletionItemProvider>>();
|
||||
for (const support of CompletionProviderRegistry.all(this._editor.getModel())) {
|
||||
for (const ch of support.triggerCharacters || []) {
|
||||
let set = supportsByTriggerCharacter[ch];
|
||||
let set = supportsByTriggerCharacter.get(ch);
|
||||
if (!set) {
|
||||
set = supportsByTriggerCharacter[ch] = new Set();
|
||||
set = new Set();
|
||||
set.add(getSnippetSuggestSupport());
|
||||
supportsByTriggerCharacter.set(ch, set);
|
||||
}
|
||||
set.add(support);
|
||||
}
|
||||
}
|
||||
|
||||
this._triggerCharacterListener = this._editor.onDidType(text => {
|
||||
const lastChar = text.charAt(text.length - 1);
|
||||
const supports = supportsByTriggerCharacter[lastChar];
|
||||
|
||||
const checkTriggerCharacter = (text?: string) => {
|
||||
|
||||
if (!text) {
|
||||
// came here from the compositionEnd-event
|
||||
const position = this._editor.getPosition()!;
|
||||
const model = this._editor.getModel()!;
|
||||
text = model.getLineContent(position.lineNumber).substr(0, position.column - 1);
|
||||
}
|
||||
|
||||
let lastChar = '';
|
||||
if (isLowSurrogate(text.charCodeAt(text.length - 1))) {
|
||||
if (isHighSurrogate(text.charCodeAt(text.length - 2))) {
|
||||
lastChar = text.substr(text.length - 2);
|
||||
}
|
||||
} else {
|
||||
lastChar = text.charAt(text.length - 1);
|
||||
}
|
||||
|
||||
const supports = supportsByTriggerCharacter.get(lastChar);
|
||||
if (supports) {
|
||||
// keep existing items that where not computed by the
|
||||
// supports/providers that want to trigger now
|
||||
const items: CompletionItem[] | undefined = this._completionModel ? this._completionModel.adopt(supports) : undefined;
|
||||
this.trigger({ auto: true, shy: false, triggerCharacter: lastChar }, Boolean(this._completionModel), supports, items);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
this._triggerCharacterListener.add(this._editor.onDidType(checkTriggerCharacter));
|
||||
this._triggerCharacterListener.add(this._editor.onCompositionEnd(checkTriggerCharacter));
|
||||
}
|
||||
|
||||
// --- trigger/retrigger/cancel suggest
|
||||
|
||||
@@ -11,7 +11,7 @@ import { onUnexpectedError, onUnexpectedExternalError } from 'vs/base/common/err
|
||||
import { KeyCode, KeyMod } from 'vs/base/common/keyCodes';
|
||||
import { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import { IActiveCodeEditor, ICodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { EditorAction, IActionOptions, registerDefaultLanguageCommand, registerEditorAction, registerEditorContribution } from 'vs/editor/browser/editorExtensions';
|
||||
import { EditorAction, IActionOptions, registerEditorAction, registerEditorContribution, registerModelAndPositionCommand } from 'vs/editor/browser/editorExtensions';
|
||||
import { CursorChangeReason, ICursorPositionChangedEvent } from 'vs/editor/common/controller/cursorEvents';
|
||||
import { Position } from 'vs/editor/common/core/position';
|
||||
import { Range } from 'vs/editor/common/core/range';
|
||||
@@ -155,7 +155,7 @@ function computeOccurencesAtPosition(model: ITextModel, selection: Selection, wo
|
||||
return new TextualOccurenceAtPositionRequest(model, selection, wordSeparators);
|
||||
}
|
||||
|
||||
registerDefaultLanguageCommand('_executeDocumentHighlights', (model, position) => getOccurrencesAtPosition(model, position, CancellationToken.None));
|
||||
registerModelAndPositionCommand('_executeDocumentHighlights', (model, position) => getOccurrencesAtPosition(model, position, CancellationToken.None));
|
||||
|
||||
class WordHighlighter {
|
||||
|
||||
|
||||
@@ -305,6 +305,7 @@ export class StandaloneKeybindingService extends AbstractKeybindingService {
|
||||
let shouldPreventDefault = this._dispatch(keyEvent, keyEvent.target);
|
||||
if (shouldPreventDefault) {
|
||||
keyEvent.preventDefault();
|
||||
keyEvent.stopPropagation();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -424,7 +424,7 @@ export function registerCodeLensProvider(languageId: string, provider: modes.Cod
|
||||
*/
|
||||
export function registerCodeActionProvider(languageId: string, provider: CodeActionProvider): IDisposable {
|
||||
return modes.CodeActionProviderRegistry.register(languageId, {
|
||||
provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise<modes.CodeActionList> => {
|
||||
provideCodeActions: (model: model.ITextModel, range: Range, context: modes.CodeActionContext, token: CancellationToken): modes.ProviderResult<modes.CodeActionList> => {
|
||||
let markers = StaticServices.markerService.get().read({ resource: model.uri }).filter(m => {
|
||||
return Range.areIntersectingOrTouching(m, range);
|
||||
});
|
||||
@@ -521,7 +521,7 @@ export interface CodeActionProvider {
|
||||
/**
|
||||
* Provide commands for the given document and range.
|
||||
*/
|
||||
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): modes.CodeActionList | Promise<modes.CodeActionList>;
|
||||
provideCodeActions(model: model.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): modes.ProviderResult<modes.CodeActionList>;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -60,6 +60,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
|
||||
verticalHasArrows: input.verticalScrollbarHasArrows,
|
||||
horizontalHasArrows: false,
|
||||
handleMouseWheel: EditorOptions.scrollbar.defaultValue.handleMouseWheel,
|
||||
alwaysConsumeMouseWheel: true,
|
||||
horizontalScrollbarSize: input.horizontalScrollbarHeight,
|
||||
horizontalSliderSize: EditorOptions.scrollbar.defaultValue.horizontalSliderSize,
|
||||
verticalScrollbarSize: input.verticalScrollbarWidth,
|
||||
|
||||
+5
-5
@@ -728,7 +728,7 @@ var AMDLoader;
|
||||
var result = compileWrapper.apply(this.exports, args);
|
||||
// cached data aftermath
|
||||
that._handleCachedData(script, scriptSource, cachedDataPath, !options.cachedData, moduleManager);
|
||||
that._verifyCachedData(script, scriptSource, cachedDataPath, hashData);
|
||||
that._verifyCachedData(script, scriptSource, cachedDataPath, hashData, moduleManager);
|
||||
return result;
|
||||
};
|
||||
};
|
||||
@@ -775,7 +775,7 @@ var AMDLoader;
|
||||
var scriptOpts = { filename: vmScriptPathOrUri_1, cachedData: cachedData };
|
||||
var script = _this._createAndEvalScript(moduleManager, scriptSource, scriptOpts, callback, errorback);
|
||||
_this._handleCachedData(script, scriptSource, cachedDataPath_1, wantsCachedData_1 && !cachedData, moduleManager);
|
||||
_this._verifyCachedData(script, scriptSource, cachedDataPath_1, hashData);
|
||||
_this._verifyCachedData(script, scriptSource, cachedDataPath_1, hashData, moduleManager);
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -906,7 +906,7 @@ var AMDLoader;
|
||||
});
|
||||
}
|
||||
};
|
||||
NodeScriptLoader.prototype._verifyCachedData = function (script, scriptSource, cachedDataPath, hashData) {
|
||||
NodeScriptLoader.prototype._verifyCachedData = function (script, scriptSource, cachedDataPath, hashData, moduleManager) {
|
||||
var _this = this;
|
||||
if (!hashData) {
|
||||
// nothing to do
|
||||
@@ -922,8 +922,8 @@ var AMDLoader;
|
||||
// for violations of this contract.
|
||||
var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest();
|
||||
if (!hashData.equals(hashDataNow)) {
|
||||
console.warn("FAILED TO VERIFY CACHED DATA. Deleting '" + cachedDataPath + "' now, but a RESTART IS REQUIRED");
|
||||
_this._fs.unlink(cachedDataPath, function (err) { return console.error("FAILED to unlink: '" + cachedDataPath + "'", err); });
|
||||
moduleManager.getConfig().onError(new Error("FAILED TO VERIFY CACHED DATA, deleting stale '" + cachedDataPath + "' now, but a RESTART IS REQUIRED"));
|
||||
_this._fs.unlink(cachedDataPath, function (err) { return moduleManager.getConfig().onError(err); });
|
||||
}
|
||||
}, Math.ceil(5000 * (1 + Math.random())));
|
||||
};
|
||||
|
||||
Vendored
+11
-4
@@ -380,6 +380,7 @@ declare namespace monaco {
|
||||
export interface IMarkdownString {
|
||||
readonly value: string;
|
||||
readonly isTrusted?: boolean;
|
||||
readonly supportThemeIcons?: boolean;
|
||||
uris?: {
|
||||
[href: string]: UriComponents;
|
||||
};
|
||||
@@ -3363,6 +3364,11 @@ declare namespace monaco.editor {
|
||||
* Defaults to true.
|
||||
*/
|
||||
handleMouseWheel?: boolean;
|
||||
/**
|
||||
* Always consume mouse wheel events (always call preventDefault() and stopPropagation() on the browser events).
|
||||
* Defaults to true.
|
||||
*/
|
||||
alwaysConsumeMouseWheel?: boolean;
|
||||
/**
|
||||
* Height in pixels for the horizontal scrollbar.
|
||||
* Defaults to 10 (px).
|
||||
@@ -3393,6 +3399,7 @@ declare namespace monaco.editor {
|
||||
readonly verticalHasArrows: boolean;
|
||||
readonly horizontalHasArrows: boolean;
|
||||
readonly handleMouseWheel: boolean;
|
||||
readonly alwaysConsumeMouseWheel: boolean;
|
||||
readonly horizontalScrollbarSize: number;
|
||||
readonly horizontalSliderSize: number;
|
||||
readonly verticalScrollbarSize: number;
|
||||
@@ -4522,7 +4529,7 @@ declare namespace monaco.languages {
|
||||
/**
|
||||
* Provide commands for the given document and range.
|
||||
*/
|
||||
provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): CodeActionList | Promise<CodeActionList>;
|
||||
provideCodeActions(model: editor.ITextModel, range: Range, context: CodeActionContext, token: CancellationToken): ProviderResult<CodeActionList>;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5521,9 +5528,9 @@ declare namespace monaco.languages {
|
||||
}
|
||||
|
||||
export interface ResourceFileEdit {
|
||||
oldUri: Uri;
|
||||
newUri: Uri;
|
||||
options: {
|
||||
oldUri?: Uri;
|
||||
newUri?: Uri;
|
||||
options?: {
|
||||
overwrite?: boolean;
|
||||
ignoreIfNotExists?: boolean;
|
||||
ignoreIfExists?: boolean;
|
||||
|
||||
@@ -796,6 +796,7 @@ export interface IFilesConfiguration {
|
||||
eol: string;
|
||||
enableTrash: boolean;
|
||||
hotExit: string;
|
||||
preventSaveConflicts: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -484,15 +484,15 @@ export class DiskFileSystemProvider extends Disposable implements
|
||||
|
||||
//#region File Watching
|
||||
|
||||
private _onDidWatchErrorOccur: Emitter<string> = this._register(new Emitter<string>());
|
||||
readonly onDidErrorOccur: Event<string> = this._onDidWatchErrorOccur.event;
|
||||
private _onDidWatchErrorOccur = this._register(new Emitter<string>());
|
||||
readonly onDidErrorOccur = this._onDidWatchErrorOccur.event;
|
||||
|
||||
private _onDidChangeFile = this._register(new Emitter<readonly IFileChange[]>());
|
||||
get onDidChangeFile(): Event<readonly IFileChange[]> { return this._onDidChangeFile.event; }
|
||||
readonly onDidChangeFile = this._onDidChangeFile.event;
|
||||
|
||||
private recursiveWatcher: WindowsWatcherService | UnixWatcherService | NsfwWatcherService | undefined;
|
||||
private recursiveFoldersToWatch: { path: string, excludes: string[] }[] = [];
|
||||
private recursiveWatchRequestDelayer: ThrottledDelayer<void> = this._register(new ThrottledDelayer<void>(0));
|
||||
private recursiveWatchRequestDelayer = this._register(new ThrottledDelayer<void>(0));
|
||||
|
||||
private recursiveWatcherLogLevelListener: IDisposable | undefined;
|
||||
|
||||
|
||||
@@ -42,7 +42,7 @@ suite('Files', () => {
|
||||
assert.strictEqual(true, r1.gotDeleted());
|
||||
});
|
||||
|
||||
function testIsEqual(testMethod: (pA: string | undefined, pB: string, ignoreCase: boolean) => boolean): void {
|
||||
function testIsEqual(testMethod: (pA: string, pB: string, ignoreCase: boolean) => boolean): void {
|
||||
|
||||
// corner cases
|
||||
assert(testMethod('', '', true));
|
||||
@@ -136,7 +136,7 @@ suite('Files', () => {
|
||||
test('isEqualOrParent (ignorecase)', function () {
|
||||
|
||||
// same assertions apply as with isEqual()
|
||||
testIsEqual(isEqualOrParent);
|
||||
testIsEqual(isEqualOrParent); //
|
||||
|
||||
if (isWindows) {
|
||||
assert(isEqualOrParent('c:\\some\\path', 'c:\\', true));
|
||||
@@ -182,4 +182,4 @@ suite('Files', () => {
|
||||
assert(!isEqualOrParent('foo/bar/test.ts', 'foo/BAR/test.', true));
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -22,7 +22,7 @@ import { IEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { createDecorator, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { attachListStyler, computeStyles, defaultListStyles, IColorMapping, attachStyler } from 'vs/platform/theme/common/styler';
|
||||
import { attachListStyler, computeStyles, defaultListStyles, IColorMapping } from 'vs/platform/theme/common/styler';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { InputFocusedContextKey } from 'vs/platform/contextkey/common/contextkeys';
|
||||
import { ObjectTree, IObjectTreeOptions, ICompressibleTreeRenderer, CompressibleObjectTree, ICompressibleObjectTreeOptions } from 'vs/base/browser/ui/tree/objectTree';
|
||||
@@ -287,7 +287,7 @@ export class WorkbenchList<T> extends List<T> {
|
||||
this.disposables.add((listService as ListService).register(this));
|
||||
|
||||
if (options.overrideStyles) {
|
||||
this.disposables.add(attachStyler(themeService, options.overrideStyles, this));
|
||||
this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
|
||||
}
|
||||
|
||||
this.disposables.add(this.onSelectionChange(() => {
|
||||
@@ -368,7 +368,7 @@ export class WorkbenchPagedList<T> extends PagedList<T> {
|
||||
this.disposables.add((listService as ListService).register(this));
|
||||
|
||||
if (options.overrideStyles) {
|
||||
this.disposables.add(attachStyler(themeService, options.overrideStyles, this));
|
||||
this.disposables.add(attachListStyler(this, themeService, options.overrideStyles));
|
||||
}
|
||||
|
||||
this.registerListeners();
|
||||
@@ -1044,7 +1044,7 @@ class WorkbenchTreeInternals<TInput, T, TFilterData> {
|
||||
this.disposables.push(
|
||||
this.contextKeyService,
|
||||
(listService as ListService).register(tree),
|
||||
overrideStyles ? attachStyler(themeService, overrideStyles, tree) : Disposable.None,
|
||||
overrideStyles ? attachListStyler(tree, themeService, overrideStyles) : Disposable.None,
|
||||
tree.onDidChangeSelection(() => {
|
||||
const selection = tree.getSelection();
|
||||
const focus = tree.getFocus();
|
||||
|
||||
@@ -102,7 +102,7 @@ export interface IProductConfiguration {
|
||||
|
||||
readonly portable?: string;
|
||||
|
||||
readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind | ExtensionKind[]; };
|
||||
readonly extensionKind?: { readonly [extensionId: string]: ExtensionKind[]; };
|
||||
readonly extensionAllowedProposedApi?: readonly string[];
|
||||
|
||||
readonly msftInternalDomains?: string[];
|
||||
|
||||
@@ -11,8 +11,9 @@ export const ITunnelService = createDecorator<ITunnelService>('tunnelService');
|
||||
|
||||
export interface RemoteTunnel {
|
||||
readonly tunnelRemotePort: number;
|
||||
readonly tunnelRemoteHost: string;
|
||||
readonly tunnelLocalPort: number;
|
||||
readonly localAddress?: string;
|
||||
readonly localAddress: string;
|
||||
dispose(): void;
|
||||
}
|
||||
|
||||
|
||||
@@ -5,11 +5,19 @@
|
||||
|
||||
import { ISignService } from 'vs/platform/sign/common/sign';
|
||||
|
||||
/* tslint:disable */
|
||||
|
||||
declare module vsda {
|
||||
export class signer {
|
||||
sign(arg: any): any;
|
||||
}
|
||||
}
|
||||
|
||||
export class SignService implements ISignService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private vsda(): Promise<typeof import('vsda')> {
|
||||
return import('vsda');
|
||||
private vsda(): Promise<typeof vsda> {
|
||||
return new Promise((resolve, reject) => require(['vsda'], resolve, reject));
|
||||
}
|
||||
|
||||
async sign(value: string): Promise<string> {
|
||||
@@ -20,9 +28,8 @@ export class SignService implements ISignService {
|
||||
return signer.sign(value);
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('signer.sign: ' + e);
|
||||
// ignore errors silently
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,16 +11,16 @@ import { isUndefined, isUndefinedOrNull } from 'vs/base/common/types';
|
||||
import { IStateService } from 'vs/platform/state/node/state';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
type StorageDatebase = { [key: string]: any; };
|
||||
type StorageDatabase = { [key: string]: any; };
|
||||
|
||||
export class FileStorage {
|
||||
|
||||
private _database: StorageDatebase | null = null;
|
||||
private _database: StorageDatabase | null = null;
|
||||
private lastFlushedSerializedDatabase: string | null = null;
|
||||
|
||||
constructor(private dbPath: string, private onError: (error: Error) => void) { }
|
||||
|
||||
private get database(): StorageDatebase {
|
||||
private get database(): StorageDatabase {
|
||||
if (!this._database) {
|
||||
this._database = this.loadSync();
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export class FileStorage {
|
||||
this._database = database;
|
||||
}
|
||||
|
||||
private loadSync(): StorageDatebase {
|
||||
private loadSync(): StorageDatabase {
|
||||
try {
|
||||
this.lastFlushedSerializedDatabase = fs.readFileSync(this.dbPath).toString();
|
||||
|
||||
@@ -56,7 +56,7 @@ export class FileStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private async loadAsync(): Promise<StorageDatebase> {
|
||||
private async loadAsync(): Promise<StorageDatabase> {
|
||||
try {
|
||||
this.lastFlushedSerializedDatabase = (await readFile(this.dbPath)).toString();
|
||||
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { values, keys } from 'vs/base/common/map';
|
||||
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
|
||||
export interface IMergeResult {
|
||||
added: ISyncExtension[];
|
||||
removed: IExtensionIdentifier[];
|
||||
updated: ISyncExtension[];
|
||||
remote: ISyncExtension[] | null;
|
||||
}
|
||||
|
||||
export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[], ignoredExtensions: string[]): IMergeResult {
|
||||
const added: ISyncExtension[] = [];
|
||||
const removed: IExtensionIdentifier[] = [];
|
||||
const updated: ISyncExtension[] = [];
|
||||
|
||||
if (!remoteExtensions) {
|
||||
return {
|
||||
added,
|
||||
removed,
|
||||
updated,
|
||||
remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase()))
|
||||
};
|
||||
}
|
||||
|
||||
const uuids: Map<string, string> = new Map<string, string>();
|
||||
const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } };
|
||||
localExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
remoteExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
if (lastSyncExtensions) {
|
||||
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
}
|
||||
|
||||
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
|
||||
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
|
||||
const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
|
||||
map.set(key, extension);
|
||||
return map;
|
||||
};
|
||||
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()) : null;
|
||||
const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
|
||||
const uuid = uuids.get(id.toLowerCase());
|
||||
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
|
||||
}, new Set<string>());
|
||||
|
||||
const localToRemote = compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { added: [], removed: [], updated: [], remote: null };
|
||||
}
|
||||
|
||||
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
|
||||
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
|
||||
|
||||
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
|
||||
const massagedExtension: ISyncExtension = {
|
||||
identifier: {
|
||||
id: extension.identifier.id,
|
||||
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
|
||||
},
|
||||
enabled: extension.enabled,
|
||||
};
|
||||
if (extension.version) {
|
||||
massagedExtension.version = extension.version;
|
||||
}
|
||||
return massagedExtension;
|
||||
};
|
||||
|
||||
// Remotely removed extension.
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
const e = localExtensionsMap.get(key);
|
||||
if (e) {
|
||||
removed.push(e.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely added extension
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
} else {
|
||||
// Add to local
|
||||
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely updated extensions
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
// If updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
// update it in local
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locally added extensions
|
||||
for (const key of values(baseToLocal.added)) {
|
||||
// Not there in remote
|
||||
if (!baseToRemote.added.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally updated extensions
|
||||
for (const key of values(baseToLocal.updated)) {
|
||||
// If removed in remote
|
||||
if (baseToRemote.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not updated in remote
|
||||
if (!baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally removed extensions
|
||||
for (const key of values(baseToLocal.removed)) {
|
||||
// If not skipped and not updated in remote
|
||||
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
|
||||
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null;
|
||||
return { added, removed, updated, remote };
|
||||
}
|
||||
|
||||
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = from ? keys(from).filter(key => !ignoredExtensions.has(key)) : [];
|
||||
const toKeys = keys(to).filter(key => !ignoredExtensions.has(key));
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const fromExtension = from!.get(key)!;
|
||||
const toExtension = to.get(key);
|
||||
if (!toExtension
|
||||
|| fromExtension.enabled !== toExtension.enabled
|
||||
|| fromExtension.version !== toExtension.version
|
||||
) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
@@ -13,12 +13,11 @@ import { joinPath } from 'vs/base/common/resources';
|
||||
import { IExtensionManagementService, IExtensionGalleryService } from 'vs/platform/extensionManagement/common/extensionManagement';
|
||||
import { ExtensionType, IExtensionIdentifier } from 'vs/platform/extensions/common/extensions';
|
||||
import { areSameExtensions } from 'vs/platform/extensionManagement/common/extensionManagementUtil';
|
||||
import { keys, values } from 'vs/base/common/map';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { Queue } from 'vs/base/common/async';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { localize } from 'vs/nls';
|
||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
|
||||
export interface ISyncPreviewResult {
|
||||
readonly added: ISyncExtension[];
|
||||
@@ -135,8 +134,14 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
|
||||
const localExtensions = await this.getLocalExtensions();
|
||||
|
||||
this.logService.trace('Extensions: Merging remote extensions with local extensions...');
|
||||
const { added, removed, updated, remote } = this.merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions);
|
||||
if (remoteExtensions) {
|
||||
this.logService.trace('Extensions: Merging remote extensions with local extensions...');
|
||||
} else {
|
||||
this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
|
||||
}
|
||||
|
||||
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
|
||||
const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, ignoredExtensions);
|
||||
|
||||
if (!added.length && !removed.length && !updated.length && !remote) {
|
||||
this.logService.trace('Extensions: No changes found during synchronizing extensions.');
|
||||
@@ -162,160 +167,6 @@ export class ExtensionsSynchroniser extends Disposable implements ISynchroniser
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge Strategy:
|
||||
* - If remote does not exist, merge with local (First time sync)
|
||||
* - Overwrite local with remote changes. Removed, Added, Updated.
|
||||
* - Update remote with those local extension which are newly added or updated or removed and untouched in remote.
|
||||
*/
|
||||
private merge(localExtensions: ISyncExtension[], remoteExtensions: ISyncExtension[] | null, lastSyncExtensions: ISyncExtension[] | null, skippedExtensions: ISyncExtension[]): { added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], remote: ISyncExtension[] | null } {
|
||||
const ignoredExtensions = this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
|
||||
// First time sync
|
||||
if (!remoteExtensions) {
|
||||
this.logService.info('Extensions: Remote extensions does not exist. Synchronizing extensions for the first time.');
|
||||
return { added: [], removed: [], updated: [], remote: localExtensions.filter(({ identifier }) => ignoredExtensions.every(id => id.toLowerCase() !== identifier.id.toLowerCase())) };
|
||||
}
|
||||
|
||||
const uuids: Map<string, string> = new Map<string, string>();
|
||||
const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } };
|
||||
localExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
remoteExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
if (lastSyncExtensions) {
|
||||
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier));
|
||||
}
|
||||
|
||||
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
|
||||
const uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
|
||||
const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
|
||||
map.set(key, extension);
|
||||
return map;
|
||||
};
|
||||
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const remoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const newRemoteExtensionsMap = remoteExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const lastSyncExtensionsMap = lastSyncExtensions ? lastSyncExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()) : null;
|
||||
const skippedExtensionsMap = skippedExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
|
||||
const ignoredExtensionsSet = ignoredExtensions.reduce((set, id) => {
|
||||
const uuid = uuids.get(id.toLowerCase());
|
||||
return set.add(uuid ? `uuid:${uuid}` : `id:${id.toLowerCase()}`);
|
||||
}, new Set<string>());
|
||||
|
||||
const localToRemote = this.compare(localExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { added: [], removed: [], updated: [], remote: null };
|
||||
}
|
||||
|
||||
const added: ISyncExtension[] = [];
|
||||
const removed: IExtensionIdentifier[] = [];
|
||||
const updated: ISyncExtension[] = [];
|
||||
|
||||
const baseToLocal = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet) : { added: keys(localExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = lastSyncExtensionsMap ? this.compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet) : { added: keys(remoteExtensionsMap).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
|
||||
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
|
||||
return {
|
||||
identifier: {
|
||||
id: extension.identifier.id,
|
||||
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
|
||||
},
|
||||
enabled: extension.enabled,
|
||||
version: extension.version
|
||||
};
|
||||
};
|
||||
|
||||
// Remotely removed extension.
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
const e = localExtensionsMap.get(key);
|
||||
if (e) {
|
||||
removed.push(e.identifier);
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely added extension
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
} else {
|
||||
// Add to local
|
||||
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Remotely updated extensions
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
// If updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Is different from local to remote
|
||||
if (localToRemote.updated.has(key)) {
|
||||
// update it in local
|
||||
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Locally added extensions
|
||||
for (const key of values(baseToLocal.added)) {
|
||||
// Not there in remote
|
||||
if (!baseToRemote.added.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally updated extensions
|
||||
for (const key of values(baseToLocal.updated)) {
|
||||
// If removed in remote
|
||||
if (baseToRemote.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If not updated in remote
|
||||
if (!baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
|
||||
}
|
||||
}
|
||||
|
||||
// Locally removed extensions
|
||||
for (const key of values(baseToLocal.removed)) {
|
||||
// If not skipped and not updated in remote
|
||||
if (!skippedExtensionsMap.has(key) && !baseToRemote.updated.has(key)) {
|
||||
newRemoteExtensionsMap.delete(key);
|
||||
}
|
||||
}
|
||||
|
||||
const remoteChanges = this.compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
|
||||
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null;
|
||||
return { added, removed, updated, remote };
|
||||
}
|
||||
|
||||
private compare(from: Map<string, ISyncExtension>, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = keys(from).filter(key => !ignoredExtensions.has(key));
|
||||
const toKeys = keys(to).filter(key => !ignoredExtensions.has(key));
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const fromExtension = from.get(key)!;
|
||||
const toExtension = to.get(key);
|
||||
if (!toExtension
|
||||
|| fromExtension.enabled !== toExtension.enabled
|
||||
|| fromExtension.version !== toExtension.version
|
||||
) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
private async updateLocalExtensions(added: ISyncExtension[], removed: IExtensionIdentifier[], updated: ISyncExtension[], skippedExtensions: ISyncExtension[]): Promise<ISyncExtension[]> {
|
||||
const removeFromSkipped: IExtensionIdentifier[] = [];
|
||||
const addToSkipped: ISyncExtension[] = [];
|
||||
|
||||
@@ -19,6 +19,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { OS, OperatingSystem } from 'vs/base/common/platform';
|
||||
import { isUndefined } from 'vs/base/common/types';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
interface ISyncContent {
|
||||
mac?: string;
|
||||
@@ -217,7 +218,7 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|
||||
|| lastSyncContent !== remoteContent // Remote has forwarded
|
||||
) {
|
||||
this.logService.trace('Keybindings: Merging remote keybindings with local keybindings...');
|
||||
const formattingOptions = await this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource);
|
||||
const formattingOptions = await this.getFormattingOptions();
|
||||
const result = await merge(localContent, remoteContent, lastSyncContent, formattingOptions, this.userDataSyncUtilService);
|
||||
// Sync only if there are changes
|
||||
if (result.hasChanges) {
|
||||
@@ -243,6 +244,14 @@ export class KeybindingsSynchroniser extends Disposable implements ISynchroniser
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
|
||||
private getFormattingOptions(): Promise<FormattingOptions> {
|
||||
if (!this._formattingOptions) {
|
||||
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.keybindingsResource);
|
||||
}
|
||||
return this._formattingOptions;
|
||||
}
|
||||
|
||||
private async getLocalContent(): Promise<IFileContent | null> {
|
||||
try {
|
||||
return await this.fileService.readFile(this.environmentService.keybindingsResource);
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
export class UserDataSycnUtilServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncUtilService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]);
|
||||
case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0]));
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
return this.channel.call('resolveUserKeybindings', [userbindings]);
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file: URI): Promise<FormattingOptions> {
|
||||
return this.channel.call('resolveFormattingOptions', [file]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,191 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { parse, findNodeAtLocation, parseTree, Node } from 'vs/base/common/json';
|
||||
import { setProperty } from 'vs/base/common/jsonEdit';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import * as contentUtil from 'vs/platform/userDataSync/common/content';
|
||||
|
||||
export function computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[], formattingOptions: FormattingOptions): string {
|
||||
if (ignoredSettings.length) {
|
||||
const remote = parse(remoteContent);
|
||||
const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set<string>());
|
||||
for (const key of ignoredSettings) {
|
||||
if (ignored.has(key)) {
|
||||
localContent = contentUtil.edit(localContent, [key], remote[key], formattingOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
return localContent;
|
||||
}
|
||||
|
||||
export function merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[], formattingOptions: FormattingOptions): { mergeContent: string, hasChanges: boolean, hasConflicts: boolean } {
|
||||
const local = parse(localContent);
|
||||
const remote = parse(remoteContent);
|
||||
const base = baseContent ? parse(baseContent) : null;
|
||||
const ignored = ignoredSettings.reduce((set, key) => { set.add(key); return set; }, new Set<string>());
|
||||
|
||||
const localToRemote = compare(local, remote, ignored);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return { mergeContent: localContent, hasChanges: false, hasConflicts: false };
|
||||
}
|
||||
|
||||
const conflicts: Set<string> = new Set<string>();
|
||||
const baseToLocal = base ? compare(base, local, ignored) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToRemote = base ? compare(base, remote, ignored) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
let mergeContent = localContent;
|
||||
|
||||
// Removed settings in Local
|
||||
for (const key of values(baseToLocal.removed)) {
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
// Removed settings in Remote
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
} else {
|
||||
mergeContent = contentUtil.edit(mergeContent, [key], undefined, formattingOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in Local
|
||||
for (const key of values(baseToLocal.added)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in remote
|
||||
if (baseToRemote.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Added settings in remote
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got added in local
|
||||
if (baseToLocal.added.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Local
|
||||
for (const key of values(baseToLocal.updated)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in remote
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Updated settings in Remote
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
if (conflicts.has(key)) {
|
||||
continue;
|
||||
}
|
||||
// Got updated in local
|
||||
if (baseToLocal.updated.has(key)) {
|
||||
// Has different value
|
||||
if (localToRemote.updated.has(key)) {
|
||||
conflicts.add(key);
|
||||
}
|
||||
} else {
|
||||
mergeContent = contentUtil.edit(mergeContent, [key], remote[key], formattingOptions);
|
||||
}
|
||||
}
|
||||
|
||||
if (conflicts.size > 0) {
|
||||
const conflictNodes: { key: string, node: Node | undefined }[] = [];
|
||||
const tree = parseTree(mergeContent);
|
||||
const eol = formattingOptions.eol!;
|
||||
for (const key of values(conflicts)) {
|
||||
const node = findNodeAtLocation(tree, [key]);
|
||||
conflictNodes.push({ key, node });
|
||||
}
|
||||
conflictNodes.sort((a, b) => {
|
||||
if (a.node && b.node) {
|
||||
return b.node.offset - a.node.offset;
|
||||
}
|
||||
return a.node ? 1 : -1;
|
||||
});
|
||||
const lastNode = tree.children ? tree.children[tree.children.length - 1] : undefined;
|
||||
for (const { key, node } of conflictNodes) {
|
||||
const remoteEdit = setProperty(`{${eol}\t${eol}}`, [key], remote[key], { tabSize: 4, insertSpaces: false, eol: eol })[0];
|
||||
const remoteContent = remoteEdit ? `${remoteEdit.content.substring(remoteEdit.offset + remoteEdit.length + 1)},${eol}` : '';
|
||||
if (node) {
|
||||
// Updated in Local and Remote with different value
|
||||
const localStartOffset = contentUtil.getLineStartOffset(mergeContent, eol, node.parent!.offset);
|
||||
const localEndOffset = contentUtil.getLineEndOffset(mergeContent, eol, node.offset + node.length);
|
||||
mergeContent = mergeContent.substring(0, localStartOffset)
|
||||
+ `<<<<<<< local${eol}`
|
||||
+ mergeContent.substring(localStartOffset, localEndOffset)
|
||||
+ `${eol}=======${eol}${remoteContent}>>>>>>> remote`
|
||||
+ mergeContent.substring(localEndOffset);
|
||||
} else {
|
||||
// Removed in Local, but updated in Remote
|
||||
if (lastNode) {
|
||||
const localStartOffset = contentUtil.getLineEndOffset(mergeContent, eol, lastNode.offset + lastNode.length);
|
||||
mergeContent = mergeContent.substring(0, localStartOffset)
|
||||
+ `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote`
|
||||
+ mergeContent.substring(localStartOffset);
|
||||
} else {
|
||||
const localStartOffset = tree.offset + 1;
|
||||
mergeContent = mergeContent.substring(0, localStartOffset)
|
||||
+ `${eol}<<<<<<< local${eol}=======${eol}${remoteContent}>>>>>>> remote${eol}`
|
||||
+ mergeContent.substring(localStartOffset);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { mergeContent, hasChanges: true, hasConflicts: conflicts.size > 0 };
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>, ignored: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
const fromKeys = Object.keys(from).filter(key => !ignored.has(key));
|
||||
const toKeys = Object.keys(to).filter(key => !ignored.has(key));
|
||||
const added = toKeys.filter(key => fromKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const removed = fromKeys.filter(key => toKeys.indexOf(key) === -1).reduce((r, key) => { r.add(key); return r; }, new Set<string>());
|
||||
const updated: Set<string> = new Set<string>();
|
||||
|
||||
for (const key of fromKeys) {
|
||||
if (removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const value1 = from[key];
|
||||
const value2 = to[key];
|
||||
if (!objects.equals(value1, value2)) {
|
||||
updated.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IFileService, FileSystemProviderErrorCode, FileSystemProviderError, IFileContent } from 'vs/platform/files/common/files';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, ISettingsMergeService, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserData, UserDataSyncStoreError, UserDataSyncStoreErrorCode, ISynchroniser, SyncStatus, IUserDataSyncStoreService, DEFAULT_IGNORED_SETTINGS, IUserDataSyncLogService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { parse, ParseError } from 'vs/base/common/json';
|
||||
import { localize } from 'vs/nls';
|
||||
@@ -17,6 +17,8 @@ import { joinPath, dirname } from 'vs/base/common/resources';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { computeRemoteContent, merge } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly fileContent: IFileContent | null;
|
||||
@@ -46,8 +48,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
@IFileService private readonly fileService: IFileService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IUserDataSyncStoreService private readonly userDataSyncStoreService: IUserDataSyncStoreService,
|
||||
@ISettingsMergeService private readonly settingsMergeService: ISettingsMergeService,
|
||||
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
|
||||
@IUserDataSyncUtilService private readonly userDataSyncUtilService: IUserDataSyncUtilService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
) {
|
||||
super();
|
||||
@@ -148,7 +150,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
await this.writeToLocal(content, fileContent);
|
||||
}
|
||||
if (hasRemoteChanged) {
|
||||
const remoteContent = remoteUserData.content ? await this.settingsMergeService.computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content)) : content;
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const remoteContent = remoteUserData.content ? computeRemoteContent(content, remoteUserData.content, this.getIgnoredSettings(content), formatUtils) : content;
|
||||
this.logService.info('Settings: Updating remote settings');
|
||||
const ref = await this.writeToRemote(remoteContent, remoteUserData.ref);
|
||||
remoteUserData = { ref, content };
|
||||
@@ -205,7 +208,8 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
|| lastSyncData.content !== remoteContent // Remote has forwarded
|
||||
) {
|
||||
this.logService.trace('Settings: Merging remote settings with local settings...');
|
||||
const result = await this.settingsMergeService.merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings());
|
||||
const formatUtils = await this.getFormattingOptions();
|
||||
const result = merge(localContent, remoteContent, lastSyncData ? lastSyncData.content : null, this.getIgnoredSettings(), formatUtils);
|
||||
// Sync only if there are changes
|
||||
if (result.hasChanges) {
|
||||
hasLocalChanged = result.mergeContent !== localContent;
|
||||
@@ -230,6 +234,14 @@ export class SettingsSynchroniser extends Disposable implements ISynchroniser {
|
||||
return { fileContent, remoteUserData, hasLocalChanged, hasRemoteChanged, hasConflicts };
|
||||
}
|
||||
|
||||
private _formattingOptions: Promise<FormattingOptions> | undefined = undefined;
|
||||
private getFormattingOptions(): Promise<FormattingOptions> {
|
||||
if (!this._formattingOptions) {
|
||||
this._formattingOptions = this.userDataSyncUtilService.resolveFormattingOptions(this.environmentService.settingsResource);
|
||||
}
|
||||
return this._formattingOptions;
|
||||
}
|
||||
|
||||
private getIgnoredSettings(settingsContent?: string): string[] {
|
||||
let value: string[] = [];
|
||||
if (settingsContent) {
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IChannel, IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ISettingsMergeService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
|
||||
export class SettingsMergeChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: ISettingsMergeService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'merge': return this.service.merge(args[0], args[1], args[2], args[3]);
|
||||
case 'computeRemoteContent': return this.service.computeRemoteContent(args[0], args[1], args[2]);
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class SettingsMergeChannelClient implements ISettingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }> {
|
||||
return this.channel.call('merge', [localContent, remoteContent, baseContent, ignoredSettings]);
|
||||
}
|
||||
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string> {
|
||||
return this.channel.call('computeRemoteContent', [localContent, remoteContent, ignoredSettings]);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -177,18 +177,6 @@ export interface IUserDataSyncService extends ISynchroniser {
|
||||
removeExtension(identifier: IExtensionIdentifier): Promise<void>;
|
||||
}
|
||||
|
||||
export const ISettingsMergeService = createDecorator<ISettingsMergeService>('ISettingsMergeService');
|
||||
|
||||
export interface ISettingsMergeService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
merge(localContent: string, remoteContent: string, baseContent: string | null, ignoredSettings: string[]): Promise<{ mergeContent: string, hasChanges: boolean, hasConflicts: boolean }>;
|
||||
|
||||
computeRemoteContent(localContent: string, remoteContent: string, ignoredSettings: string[]): Promise<string>;
|
||||
|
||||
}
|
||||
|
||||
export const IUserDataSyncUtilService = createDecorator<IUserDataSyncUtilService>('IUserDataSyncUtilService');
|
||||
|
||||
export interface IUserDataSyncUtilService {
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IUserDataSyncService, IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
|
||||
export class UserDataSyncChannel implements IServerChannel {
|
||||
|
||||
@@ -30,3 +33,38 @@ export class UserDataSyncChannel implements IServerChannel {
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSycnUtilServiceChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IUserDataSyncUtilService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
throw new Error(`Event not found: ${event}`);
|
||||
}
|
||||
|
||||
call(context: any, command: string, args?: any): Promise<any> {
|
||||
switch (command) {
|
||||
case 'resolveUserKeybindings': return this.service.resolveUserBindings(args[0]);
|
||||
case 'resolveFormattingOptions': return this.service.resolveFormattingOptions(URI.revive(args[0]));
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
}
|
||||
|
||||
async resolveUserBindings(userbindings: string[]): Promise<IStringDictionary<string>> {
|
||||
return this.channel.call('resolveUserKeybindings', [userbindings]);
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file: URI): Promise<FormattingOptions> {
|
||||
return this.channel.call('resolveFormattingOptions', [file]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,500 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { ISyncExtension } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
|
||||
|
||||
suite('ExtensionsMerge - No Conflicts', () => {
|
||||
|
||||
test('merge returns local extension if remote does not exist', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, localExtensions);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with ignored extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, [], ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, [], ['A']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with skipped extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const skippedExtension: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, skippedExtension, []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge returns local extension if remote does not exist with skipped and ignored extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const skippedExtension: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, null, null, skippedExtension, ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when there is no base', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, null, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when there is no base and with ignored extensions', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, null, [], ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote is moved forwarded', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote moved forwarded with ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a']);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote is moved forwarded with skipped extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.equal(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, localExtensions);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded with ignored settings', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, [
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
]);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded with skipped extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded with ignored extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded with skipped extensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', async () => {
|
||||
const baseExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const skippedExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
];
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']);
|
||||
|
||||
assert.deepEqual(actual.added, []);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
test('merge when remote extension has no uuid and different extension id case', async () => {
|
||||
const localExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
const remoteExtensions: ISyncExtension[] = [
|
||||
{ identifier: { id: 'A' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
];
|
||||
const expected: ISyncExtension[] = [
|
||||
{ identifier: { id: 'A' }, enabled: true },
|
||||
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
|
||||
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
|
||||
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
|
||||
];
|
||||
|
||||
const actual = merge(localExtensions, remoteExtensions, null, [], []);
|
||||
|
||||
assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, enabled: true }]);
|
||||
assert.deepEqual(actual.removed, []);
|
||||
assert.deepEqual(actual.updated, []);
|
||||
assert.deepEqual(actual.remote, expected);
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
@@ -6,7 +6,6 @@
|
||||
import * as assert from 'assert';
|
||||
import { merge } from 'vs/platform/userDataSync/common/keybindingsMerge';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { OperatingSystem, OS } from 'vs/base/common/platform';
|
||||
import { IUserDataSyncUtilService } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { FormattingOptions } from 'vs/base/common/jsonFormatter';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -441,7 +440,7 @@ suite('KeybindingsMerge - No Conflicts', () => {
|
||||
});
|
||||
|
||||
|
||||
suite.skip('KeybindingsMerge - Conflicts', () => {
|
||||
suite('KeybindingsMerge - Conflicts', () => {
|
||||
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
const localContent = stringify([{ key: 'alt+d', command: 'a', when: 'editorTextFocus && !editorReadonly' }]);
|
||||
@@ -730,6 +729,6 @@ class MockUserDataSyncUtilService implements IUserDataSyncUtilService {
|
||||
}
|
||||
|
||||
async resolveFormattingOptions(file?: URI): Promise<FormattingOptions> {
|
||||
return { eol: OS === OperatingSystem.Windows ? '\r\n' : '\n', insertSpaces: false, tabSize: 4 };
|
||||
return { eol: '\n', insertSpaces: false, tabSize: 4 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,547 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { merge, computeRemoteContent } from 'vs/platform/userDataSync/common/settingsMerge';
|
||||
|
||||
const formattingOptions = { eol: '\n', insertSpaces: false, tabSize: 4 };
|
||||
|
||||
suite('SettingsMerge - No Conflicts', () => {
|
||||
|
||||
test('merge when local and remote are same with one entry', async () => {
|
||||
const localContent = stringify({ 'a': 1 });
|
||||
const remoteContent = stringify({ 'a': 1 });
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries in different order', async () => {
|
||||
const localContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with different base content', async () => {
|
||||
const localContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
});
|
||||
const baseContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
'c': 3,
|
||||
});
|
||||
const expected = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'b': 2,
|
||||
'a': 1,
|
||||
'c': 3,
|
||||
});
|
||||
const expected = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expected);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from remote from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when all entries are removed from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.deepEqual(JSON.parse(actual.mergeContent), {});
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in remote from base and local has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when remote has moved forwareded with multiple changes and local stays with base', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, localContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, remoteContent);
|
||||
});
|
||||
|
||||
test('merge when a new entries are added to local', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to local from base and remote is not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from local from base and remote has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'c': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in local from base and remote has not changed', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'c': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2,
|
||||
'c': 2,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('merge when local has moved forwarded with multiple changes and remote stays with base', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 2,
|
||||
'b': 1,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 1,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, remoteContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('SettingsMerge - Conflicts', () => {
|
||||
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, null, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
"a": 1
|
||||
=======
|
||||
"a": 2,
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 1
|
||||
});
|
||||
const localContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'b': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
"a": 2,
|
||||
=======
|
||||
>>>>>>> remote
|
||||
"b": 2
|
||||
}`);
|
||||
});
|
||||
|
||||
test('merge with single entry and local is empty', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 1
|
||||
});
|
||||
const localContent = stringify({});
|
||||
const remoteContent = stringify({
|
||||
'a': 2
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
=======
|
||||
"a": 2,
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
test('merge when local and remote has moved forwareded with conflicts', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
});
|
||||
const localContent = stringify({
|
||||
'a': 2,
|
||||
'c': 3,
|
||||
'd': 5,
|
||||
'e': 4,
|
||||
'f': 1,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'b': 3,
|
||||
'c': 3,
|
||||
'd': 6,
|
||||
'e': 5,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, [], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
<<<<<<< local
|
||||
"a": 2,
|
||||
=======
|
||||
>>>>>>> remote
|
||||
"c": 3,
|
||||
<<<<<<< local
|
||||
"d": 5,
|
||||
=======
|
||||
"d": 6,
|
||||
>>>>>>> remote
|
||||
<<<<<<< local
|
||||
"e": 4,
|
||||
=======
|
||||
"e": 5,
|
||||
>>>>>>> remote
|
||||
"f": 1
|
||||
<<<<<<< local
|
||||
=======
|
||||
"b": 3,
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('SettingsMerge - Ignored Settings', () => {
|
||||
|
||||
test('ignored setting is not merged when changed in local and remote', async () => {
|
||||
const localContent = stringify({ 'a': 1 });
|
||||
const remoteContent = stringify({ 'a': 2 });
|
||||
const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged when changed in local and remote from base', async () => {
|
||||
const baseContent = stringify({ 'a': 0 });
|
||||
const localContent = stringify({ 'a': 1 });
|
||||
const remoteContent = stringify({ 'a': 2 });
|
||||
const actual = merge(localContent, remoteContent, baseContent, ['a'], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged when added in remote', async () => {
|
||||
const localContent = stringify({});
|
||||
const remoteContent = stringify({ 'a': 1 });
|
||||
const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged when added in remote from base', async () => {
|
||||
const localContent = stringify({ 'b': 2 });
|
||||
const remoteContent = stringify({ 'a': 1, 'b': 2 });
|
||||
const actual = merge(localContent, remoteContent, localContent, ['a'], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged when removed in remote', async () => {
|
||||
const localContent = stringify({ 'a': 1 });
|
||||
const remoteContent = stringify({});
|
||||
const actual = merge(localContent, remoteContent, null, ['a'], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged when removed in remote from base', async () => {
|
||||
const localContent = stringify({ 'a': 2 });
|
||||
const remoteContent = stringify({});
|
||||
const actual = merge(localContent, remoteContent, localContent, ['a'], formattingOptions);
|
||||
assert.ok(!actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, localContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged with other changes without conflicts', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 2,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
'e': 5,
|
||||
});
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'b': 3,
|
||||
'd': 4,
|
||||
'e': 6,
|
||||
});
|
||||
const expectedContent = stringify({
|
||||
'a': 1,
|
||||
'b': 3,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], formattingOptions);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(!actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent, expectedContent);
|
||||
});
|
||||
|
||||
test('ignored setting is not merged with other changes conflicts', async () => {
|
||||
const baseContent = stringify({
|
||||
'a': 2,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
'd': 4,
|
||||
'e': 5,
|
||||
});
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 4,
|
||||
'c': 3,
|
||||
'd': 5,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'b': 3,
|
||||
'e': 6,
|
||||
});
|
||||
const actual = merge(localContent, remoteContent, baseContent, ['a', 'e'], formattingOptions);
|
||||
//'{\n\t"a": 1,\n\n<<<<<<< local\t"b": 4,\n=======\n\t"b": 3,\n>>>>>>> remote'
|
||||
//'{\n\t"a": 1,\n<<<<<<< local\n\t"b": 4,\n=======\n\t"b": 3,\n>>>>>>> remote\n<<<<<<< local\n\t"d": 5\n=======\n>>>>>>> remote\n}'
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasChanges);
|
||||
assert.ok(actual.hasConflicts);
|
||||
assert.equal(actual.mergeContent,
|
||||
`{
|
||||
"a": 1,
|
||||
<<<<<<< local
|
||||
"b": 4,
|
||||
=======
|
||||
"b": 3,
|
||||
>>>>>>> remote
|
||||
<<<<<<< local
|
||||
"d": 5
|
||||
=======
|
||||
>>>>>>> remote
|
||||
}`);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
suite('SettingsMerge - Compute Remote Content', () => {
|
||||
|
||||
test('local content is returned when there are no ignored settings', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'b': 3,
|
||||
'd': 4,
|
||||
'e': 6,
|
||||
});
|
||||
const actual = computeRemoteContent(localContent, remoteContent, [], formattingOptions);
|
||||
assert.equal(actual, localContent);
|
||||
});
|
||||
|
||||
test('ignored settings are not updated from remote content', async () => {
|
||||
const localContent = stringify({
|
||||
'a': 1,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const remoteContent = stringify({
|
||||
'a': 3,
|
||||
'b': 3,
|
||||
'd': 4,
|
||||
'e': 6,
|
||||
});
|
||||
const expected = stringify({
|
||||
'a': 3,
|
||||
'b': 2,
|
||||
'c': 3,
|
||||
});
|
||||
const actual = computeRemoteContent(localContent, remoteContent, ['a'], formattingOptions);
|
||||
assert.equal(actual, expected);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function stringify(value: any): string {
|
||||
return JSON.stringify(value, null, '\t');
|
||||
}
|
||||
@@ -91,7 +91,7 @@ export interface IWindowSettings {
|
||||
titleBarStyle: 'native' | 'custom';
|
||||
autoDetectHighContrast: boolean;
|
||||
menuBarVisibility: MenuBarVisibility;
|
||||
newWindowDimensions: 'default' | 'inherit' | 'maximized' | 'fullscreen';
|
||||
newWindowDimensions: 'default' | 'inherit' | 'offset' | 'maximized' | 'fullscreen';
|
||||
nativeTabs: boolean;
|
||||
nativeFullScreen: boolean;
|
||||
enableMenuBarMnemonics: boolean;
|
||||
|
||||
@@ -1381,7 +1381,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
// Window state is not from a previous session: only allow fullscreen if we inherit it or user wants fullscreen
|
||||
let allowFullscreen: boolean;
|
||||
if (state.hasDefaultState) {
|
||||
allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit'].indexOf(windowConfig.newWindowDimensions) >= 0);
|
||||
allowFullscreen = (windowConfig?.newWindowDimensions && ['fullscreen', 'inherit', 'offset'].indexOf(windowConfig.newWindowDimensions) >= 0);
|
||||
}
|
||||
|
||||
// Window state is from a previous session: only allow fullscreen when we got updated or user wants to restore
|
||||
@@ -1576,7 +1576,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
} else if (windowConfig.newWindowDimensions === 'fullscreen') {
|
||||
state.mode = WindowMode.Fullscreen;
|
||||
ensureNoOverlap = false;
|
||||
} else if (windowConfig.newWindowDimensions === 'inherit' && lastActive) {
|
||||
} else if ((windowConfig.newWindowDimensions === 'inherit' || windowConfig.newWindowDimensions === 'offset') && lastActive) {
|
||||
const lastActiveState = lastActive.serializeWindowState();
|
||||
if (lastActiveState.mode === WindowMode.Fullscreen) {
|
||||
state.mode = WindowMode.Fullscreen; // only take mode (fixes https://github.com/Microsoft/vscode/issues/19331)
|
||||
@@ -1584,7 +1584,7 @@ export class WindowsMainService extends Disposable implements IWindowsMainServic
|
||||
state = lastActiveState;
|
||||
}
|
||||
|
||||
ensureNoOverlap = false;
|
||||
ensureNoOverlap = state.mode !== WindowMode.Fullscreen && windowConfig.newWindowDimensions === 'offset';
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Vendored
+8
-1
@@ -2341,6 +2341,12 @@ declare module 'vscode' {
|
||||
*/
|
||||
export class MarkdownString {
|
||||
|
||||
/**
|
||||
* Escapes any [ThemeIcons](#ThemeIcon), e.g. `$(zap)`, in the string.
|
||||
* @param value A string.
|
||||
*/
|
||||
static escapeThemeIcons(value: string): string;
|
||||
|
||||
/**
|
||||
* The markdown string.
|
||||
*/
|
||||
@@ -2356,8 +2362,9 @@ declare module 'vscode' {
|
||||
* Creates a new markdown string with the given value.
|
||||
*
|
||||
* @param value Optional, initial value.
|
||||
* @param options Optional, options to specify whether [ThemeIcons](#ThemeIcon) are supported within the [`MarkdownString`](#MarkdownString).
|
||||
*/
|
||||
constructor(value?: string);
|
||||
constructor(value?: string, options?: { supportThemeIcons?: boolean });
|
||||
|
||||
/**
|
||||
* Appends and escapes the given string to this markdown string.
|
||||
|
||||
Vendored
+151
-81
@@ -16,7 +16,7 @@
|
||||
|
||||
declare module 'vscode' {
|
||||
|
||||
//#region Alex - resolvers
|
||||
//#region Alex - resolvers, AlexR - ports
|
||||
|
||||
export interface RemoteAuthorityResolverContext {
|
||||
resolveAttempt: number;
|
||||
@@ -33,7 +33,30 @@ declare module 'vscode' {
|
||||
extensionHostEnv?: { [key: string]: string | null };
|
||||
}
|
||||
|
||||
export type ResolverResult = ResolvedAuthority & ResolvedOptions;
|
||||
export interface TunnelOptions {
|
||||
remote: { port: number, host: string };
|
||||
localPort?: number;
|
||||
name?: string;
|
||||
}
|
||||
|
||||
export interface Tunnel extends Disposable {
|
||||
remote: { port: number, host: string };
|
||||
localAddress: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used as part of the ResolverResult if the extension has any candidate, published, or forwarded ports.
|
||||
*/
|
||||
export interface TunnelInformation {
|
||||
/**
|
||||
* Tunnels that are detected by the extension. The remotePort is used for display purposes.
|
||||
* The localAddress should be the complete local address(ex. localhost:1234) for connecting to the port. Tunnels provided through
|
||||
* detected are read-only from the forwarded ports UI.
|
||||
*/
|
||||
detectedTunnels?: { remote: { port: number, host: string }, localAddress: string }[];
|
||||
}
|
||||
|
||||
export type ResolverResult = ResolvedAuthority & ResolvedOptions & TunnelInformation;
|
||||
|
||||
export class RemoteAuthorityResolverError extends Error {
|
||||
static NotAvailable(message?: string, handled?: boolean): RemoteAuthorityResolverError;
|
||||
@@ -44,6 +67,20 @@ declare module 'vscode' {
|
||||
|
||||
export interface RemoteAuthorityResolver {
|
||||
resolve(authority: string, context: RemoteAuthorityResolverContext): ResolverResult | Thenable<ResolverResult>;
|
||||
/**
|
||||
* Can be optionally implemented if the extension can forward ports better than the core.
|
||||
* When not implemented, the core will use its default forwarding logic.
|
||||
* When implemented, the core will use this to forward ports.
|
||||
*/
|
||||
forwardPort?(tunnelOptions: TunnelOptions): Thenable<Tunnel | undefined>;
|
||||
}
|
||||
|
||||
export namespace workspace {
|
||||
/**
|
||||
* Forwards a port. Currently only works for a remote host of localhost.
|
||||
* @param forward The `localPort` is a suggestion only. If that port is not available another will be chosen.
|
||||
*/
|
||||
export function makeTunnel(forward: TunnelOptions): Thenable<Tunnel>;
|
||||
}
|
||||
|
||||
export interface ResourceLabelFormatter {
|
||||
@@ -133,12 +170,15 @@ declare module 'vscode' {
|
||||
*/
|
||||
export interface SemanticTokensProvider {
|
||||
/**
|
||||
* A file can contain many tokens, perhaps even hundreds of thousands tokens. Therefore, to improve
|
||||
* the memory consumption around describing semantic tokens, we have decided to avoid allocating objects
|
||||
* and we have decided to represent tokens from a file as an array of integers.
|
||||
* A file can contain many tokens, perhaps even hundreds of thousands of tokens. Therefore, to improve
|
||||
* the memory consumption around describing semantic tokens, we have decided to avoid allocating an object
|
||||
* for each token and we represent tokens from a file as an array of integers. Furthermore, the position
|
||||
* of each token is expressed relative to the token before it because most tokens remain stable relative to
|
||||
* each other when edits are made in a file.
|
||||
*
|
||||
*
|
||||
* In short, each token takes 5 integers to represent, so a specific token i in the file consists of the following fields:
|
||||
* ---
|
||||
* In short, each token takes 5 integers to represent, so a specific token `i` in the file consists of the following fields:
|
||||
* - at index `5*i` - `deltaLine`: token line number, relative to the previous token
|
||||
* - at index `5*i+1` - `deltaStart`: token start character, relative to the previous token (relative to 0 or the previous token's start if they are on the same line)
|
||||
* - at index `5*i+2` - `length`: the length of the token. A token cannot be multiline.
|
||||
@@ -146,87 +186,119 @@ declare module 'vscode' {
|
||||
* - at index `5*i+4` - `tokenModifiers`: each set bit will be looked up in `SemanticTokensLegend.tokenModifiers`
|
||||
*
|
||||
*
|
||||
*
|
||||
* ---
|
||||
* ### How to encode tokens
|
||||
*
|
||||
* Here is an example for encoding a file with 3 tokens:
|
||||
* ```
|
||||
* [ { line: 2, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] },
|
||||
* { line: 2, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] },
|
||||
* { line: 2, startChar: 10, length: 4, tokenType: "types", tokenModifiers: [] },
|
||||
* { line: 5, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] } ]
|
||||
* { line: 5, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] }
|
||||
* ```
|
||||
*
|
||||
* 1. First of all, a legend must be devised. This legend must be provided up-front and capture all possible token types.
|
||||
* For this example, we will choose the following legend which is passed in when registering the provider:
|
||||
* For this example, we will choose the following legend which must be passed in when registering the provider:
|
||||
* ```
|
||||
* { tokenTypes: ['', 'properties', 'types', 'classes'],
|
||||
* tokenModifiers: ['', 'private', 'static'] }
|
||||
* tokenTypes: ['properties', 'types', 'classes'],
|
||||
* tokenModifiers: ['private', 'static']
|
||||
* ```
|
||||
*
|
||||
* 2. The first transformation is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked
|
||||
* 2. The first transformation step is to encode `tokenType` and `tokenModifiers` as integers using the legend. Token types are looked
|
||||
* up by index, so a `tokenType` value of `1` means `tokenTypes[1]`. Multiple token modifiers can be set by using bit flags,
|
||||
* so a `tokenModifier` value of `6` is first viewed as binary `0b110`, which means `[tokenModifiers[1], tokenModifiers[2]]` because
|
||||
* bits 1 and 2 are set. Using this legend, the tokens now are:
|
||||
* so a `tokenModifier` value of `3` is first viewed as binary `0b00000011`, which means `[tokenModifiers[0], tokenModifiers[1]]` because
|
||||
* bits 0 and 1 are set. Using this legend, the tokens now are:
|
||||
* ```
|
||||
* [ { line: 2, startChar: 5, length: 3, tokenType: 1, tokenModifiers: 6 }, // 6 is 0b110
|
||||
* { line: 2, startChar: 10, length: 4, tokenType: 2, tokenModifiers: 0 },
|
||||
* { line: 5, startChar: 2, length: 7, tokenType: 3, tokenModifiers: 0 } ]
|
||||
* { line: 2, startChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 },
|
||||
* { line: 2, startChar: 10, length: 4, tokenType: 1, tokenModifiers: 0 },
|
||||
* { line: 5, startChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 }
|
||||
* ```
|
||||
*
|
||||
* 3. Then, we will encode each token relative to the previous token in the file:
|
||||
* 3. The next steps is to encode each token relative to the previous token in the file. In this case, the second token
|
||||
* is on the same line as the first token, so the `startChar` of the second token is made relative to the `startChar`
|
||||
* of the first token, so it will be `10 - 5`. The third token is on a different line than the second token, so the
|
||||
* `startChar` of the third token will not be altered:
|
||||
* ```
|
||||
* [ { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 1, tokenModifiers: 6 },
|
||||
* // this token is on the same line as the first one, so the startChar is made relative
|
||||
* { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 2, tokenModifiers: 0 },
|
||||
* // this token is on a different line than the second one, so the startChar remains unchanged
|
||||
* { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 3, tokenModifiers: 0 } ]
|
||||
* { deltaLine: 2, deltaStartChar: 5, length: 3, tokenType: 0, tokenModifiers: 3 },
|
||||
* { deltaLine: 0, deltaStartChar: 5, length: 4, tokenType: 1, tokenModifiers: 0 },
|
||||
* { deltaLine: 3, deltaStartChar: 2, length: 7, tokenType: 2, tokenModifiers: 0 }
|
||||
* ```
|
||||
*
|
||||
* 4. Finally, the integers are organized in a single array, which is a memory friendly representation:
|
||||
* 4. Finally, the last step is to inline each of the 5 fields for a token in a single array, which is a memory friendly representation:
|
||||
* ```
|
||||
* // 1st token, 2nd token, 3rd token
|
||||
* [ 2,5,3,1,6, 0,5,4,2,0, 3,2,7,3,0 ]
|
||||
* [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ]
|
||||
* ```
|
||||
*
|
||||
* In principle, each call to `provideSemanticTokens` expects a complete representations of the semantic tokens.
|
||||
* It is possible to simply return all the tokens at each call.
|
||||
*
|
||||
* But oftentimes, a small edit in the file will result in a small change to the above delta-based represented tokens.
|
||||
* (In fact, that is why the above tokens are delta-encoded relative to their corresponding previous tokens).
|
||||
* In such a case, if VS Code passes in the previous result id, it is possible for an advanced tokenization provider
|
||||
* to return a delta to the integers array.
|
||||
*
|
||||
* To continue with the previous example, suppose a new line has been pressed at the beginning of the file, such that
|
||||
* all the tokens are now one line lower, and that a new token has appeared since the last result on line 4.
|
||||
* For example, the tokens might look like:
|
||||
* ---
|
||||
* ### How tokens change when the document changes
|
||||
*
|
||||
* Let's look at how tokens might change.
|
||||
*
|
||||
* Continuing with the above example, suppose a new line was inserted at the top of the file.
|
||||
* That would make all the tokens move down by one line (notice how the line has changed for each one):
|
||||
* ```
|
||||
* [ { line: 3, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] },
|
||||
* { line: 3, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] },
|
||||
* { line: 3, startChar: 10, length: 4, tokenType: "types", tokenModifiers: [] },
|
||||
* { line: 6, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] }
|
||||
* ```
|
||||
* The integer encoding of the tokens does not change substantially because of the delta-encoding of positions:
|
||||
* ```
|
||||
* // 1st token, 2nd token, 3rd token
|
||||
* [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ]
|
||||
* ```
|
||||
* It is possible to express these new tokens in terms of an edit applied to the previous tokens:
|
||||
* ```
|
||||
* [ 2,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ]
|
||||
* [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ]
|
||||
*
|
||||
* edit: { start: 0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3
|
||||
* ```
|
||||
*
|
||||
* Furthermore, let's assume that a new token has appeared on line 4:
|
||||
* ```
|
||||
* { line: 3, startChar: 5, length: 3, tokenType: "properties", tokenModifiers: ["private", "static"] },
|
||||
* { line: 3, startChar: 10, length: 4, tokenType: "types", tokenModifiers: [] },
|
||||
* { line: 4, startChar: 3, length: 5, tokenType: "properties", tokenModifiers: ["static"] },
|
||||
* { line: 6, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] } ]
|
||||
* { line: 6, startChar: 2, length: 7, tokenType: "classes", tokenModifiers: [] }
|
||||
* ```
|
||||
* The integer encoding of the tokens is:
|
||||
* ```
|
||||
* // 1st token, 2nd token, 3rd token, 4th token
|
||||
* [ 3,5,3,0,3, 0,5,4,1,0, 1,3,5,0,2, 2,2,7,2,0, ]
|
||||
* ```
|
||||
* Again, it is possible to express these new tokens in terms of an edit applied to the previous tokens:
|
||||
* ```
|
||||
* [ 3,5,3,0,3, 0,5,4,1,0, 3,2,7,2,0 ]
|
||||
* [ 3,5,3,0,3, 0,5,4,1,0, 1,3,5,0,2, 2,2,7,2,0, ]
|
||||
*
|
||||
* edit: { start: 10, deleteCount: 1, data: [1,3,5,0,2,2] } // replace integer at offset 10 with [1,3,5,0,2,2]
|
||||
* ```
|
||||
*
|
||||
* The integer encoding of all new tokens would be:
|
||||
* ```
|
||||
* [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ]
|
||||
* ```
|
||||
*
|
||||
* A smart tokens provider can return a `resultId` to `SemanticTokens`. Then, if the editor still has in memory the previous
|
||||
* result, the editor will pass in options the previous result id at `SemanticTokensRequestOptions.previousResultId`. Only when
|
||||
* the editor passes in the previous result id, it is safe and smart for a smart tokens provider can compute a diff from the
|
||||
* previous result to the new result.
|
||||
*
|
||||
* *NOTE*: It is illegal to return `SemanticTokensEdits` if `options.previousResultId` is not set!
|
||||
* ---
|
||||
* ### When to return `SemanticTokensEdits`
|
||||
*
|
||||
* ```
|
||||
* [ 2,5,3,1,6, 0,5,4,2,0, 3,2,7,3,0 ]
|
||||
* [ 3,5,3,1,6, 0,5,4,2,0, 1,3,5,1,2, 2,2,7,3,0 ]
|
||||
* ```
|
||||
* and return as simple integer edits the diff:
|
||||
* ```
|
||||
* { edits: [
|
||||
* { start: 0, deleteCount: 1, data: [3] } // replace integer at offset 0 with 3
|
||||
* { start: 10, deleteCount: 1, data: [1,3,5,1,2,2] } // replace integer at offset 10 with [1,3,5,1,2,2]
|
||||
* ]}
|
||||
* ```
|
||||
* All indices expressed in the returned diff represent indices in the old result array, so they all refer to the previous result state.
|
||||
* When doing edits, it is possible that multiple edits occur until VS Code decides to invoke the semantic tokens provider.
|
||||
* In principle, each call to `provideSemanticTokens` can return a full representations of the semantic tokens, and that would
|
||||
* be a perfectly reasonable semantic tokens provider implementation.
|
||||
*
|
||||
* However, when having a language server running in a separate process, transferring all the tokens between processes
|
||||
* might be slow, so VS Code allows to return the new tokens expressed in terms of multiple edits applied to the previous
|
||||
* tokens.
|
||||
*
|
||||
* To clearly define what "previous tokens" means, it is possible to return a `resultId` with the semantic tokens. If the
|
||||
* editor still has in memory the previous result, the editor will pass in options the previous `resultId` at
|
||||
* `SemanticTokensRequestOptions.previousResultId`. Only when the editor passes in the previous `resultId`, it is allowed
|
||||
* that a semantic tokens provider returns the new tokens expressed as edits to be applied to the previous result. Even in this
|
||||
* case, the semantic tokens provider needs to return a new `resultId` that will identify these new tokens as a basis
|
||||
* for the next request.
|
||||
*
|
||||
* *NOTE 1*: It is illegal to return `SemanticTokensEdits` if `options.previousResultId` is not set.
|
||||
* *NOTE 2*: All edits in `SemanticTokensEdits` contain indices in the old integers array, so they all refer to the previous result state.
|
||||
*/
|
||||
provideSemanticTokens(document: TextDocument, options: SemanticTokensRequestOptions, token: CancellationToken): ProviderResult<SemanticTokens | SemanticTokensEdits>;
|
||||
}
|
||||
@@ -1056,7 +1128,7 @@ declare module 'vscode' {
|
||||
}
|
||||
//#endregion
|
||||
|
||||
//#region Ben - status bar item with ID and Name
|
||||
//#region Status bar item with ID and Name: https://github.com/microsoft/vscode/issues/74972
|
||||
|
||||
export namespace window {
|
||||
|
||||
@@ -1104,26 +1176,6 @@ declare module 'vscode' {
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Ben - extension auth flow (desktop+web)
|
||||
|
||||
export interface AppUriOptions {
|
||||
payload?: {
|
||||
path?: string;
|
||||
query?: string;
|
||||
fragment?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export namespace env {
|
||||
|
||||
/**
|
||||
* @deprecated use `vscode.env.asExternalUri` instead.
|
||||
*/
|
||||
export function createAppUri(options?: AppUriOptions): Thenable<Uri>;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Custom editors: https://github.com/microsoft/vscode/issues/77131
|
||||
|
||||
/**
|
||||
@@ -1279,10 +1331,28 @@ declare module 'vscode' {
|
||||
/**
|
||||
* Marks that the code action cannot currently be applied.
|
||||
*
|
||||
* This should be a human readable description of why the code action is currently disabled. Disabled code actions
|
||||
* will be surfaced in the refactor UI but cannot be applied.
|
||||
* Disabled code actions will be surfaced in the refactor UI but cannot be applied.
|
||||
*/
|
||||
disabled?: string;
|
||||
disabled?: {
|
||||
/**
|
||||
* Human readable description of why the code action is currently disabled.
|
||||
*
|
||||
* This is displayed in the UI.
|
||||
*/
|
||||
reason: string;
|
||||
};
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Allow theme icons in hovers: https://github.com/microsoft/vscode/issues/84695
|
||||
|
||||
export interface MarkdownString {
|
||||
|
||||
/**
|
||||
* Indicates that this markdown string can contain [ThemeIcons](#ThemeIcon), e.g. `$(zap)`.
|
||||
*/
|
||||
readonly supportThemeIcons?: boolean;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -56,6 +56,7 @@ import './mainThreadWorkspace';
|
||||
import './mainThreadComments';
|
||||
// import './mainThreadTask'; {{SQL CARBON EDIT}} @anthonydresser comment out task
|
||||
import './mainThreadLabelService';
|
||||
import './mainThreadTunnelService';
|
||||
import 'vs/workbench/api/common/apiCommands';
|
||||
|
||||
export class ExtensionPoints implements IWorkbenchContribution {
|
||||
|
||||
@@ -33,7 +33,7 @@ export class MainThreadDiagnostics implements MainThreadDiagnosticsShape {
|
||||
this._activeOwners.clear();
|
||||
}
|
||||
|
||||
private _forwardMarkers(resources: URI[]): void {
|
||||
private _forwardMarkers(resources: readonly URI[]): void {
|
||||
const data: [UriComponents, IMarkerData[]][] = [];
|
||||
for (const resource of resources) {
|
||||
data.push([
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { IOutputService, IOutputChannel, OUTPUT_PANEL_ID, Extensions, IOutputChannelRegistry } from 'vs/workbench/contrib/output/common/output';
|
||||
import { IOutputService, IOutputChannel, OUTPUT_PANEL_ID } from 'vs/workbench/contrib/output/common/output';
|
||||
import { Extensions, IOutputChannelRegistry } from 'vs/workbench/services/output/common/output';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { MainThreadOutputServiceShape, MainContext, IExtHostContext, ExtHostOutputServiceShape, ExtHostContext } from '../common/extHost.protocol';
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { MainThreadTunnelServiceShape, IExtHostContext, MainContext, ExtHostContext, ExtHostTunnelServiceShape } from 'vs/workbench/api/common/extHost.protocol';
|
||||
import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
import { extHostNamedCustomer } from 'vs/workbench/api/common/extHostCustomers';
|
||||
import { IRemoteExplorerService } from 'vs/workbench/services/remote/common/remoteExplorerService';
|
||||
|
||||
@extHostNamedCustomer(MainContext.MainThreadTunnelService)
|
||||
export class MainThreadTunnelService implements MainThreadTunnelServiceShape {
|
||||
// @ts-ignore
|
||||
private readonly _proxy: ExtHostTunnelServiceShape;
|
||||
|
||||
constructor(
|
||||
extHostContext: IExtHostContext,
|
||||
@IRemoteExplorerService private readonly remoteExplorerService: IRemoteExplorerService
|
||||
) {
|
||||
this._proxy = extHostContext.getProxy(ExtHostContext.ExtHostTunnelService);
|
||||
}
|
||||
|
||||
async $openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined> {
|
||||
const tunnel = await this.remoteExplorerService.forward(tunnelOptions.remote.port, tunnelOptions.localPort, tunnelOptions.name);
|
||||
if (tunnel) {
|
||||
return { remote: { host: tunnel.tunnelRemoteHost, port: tunnel.tunnelRemotePort }, localAddress: tunnel.localAddress };
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
async $closeTunnel(remotePort: number): Promise<void> {
|
||||
return this.remoteExplorerService.close(remotePort);
|
||||
}
|
||||
|
||||
$addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): Promise<void> {
|
||||
return Promise.resolve(this.remoteExplorerService.addDetected(tunnels));
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
//
|
||||
}
|
||||
}
|
||||
@@ -72,16 +72,6 @@ export class MainThreadUrls implements MainThreadUrlsShape {
|
||||
return this.urlService.create(uri);
|
||||
}
|
||||
|
||||
async $proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial<UriComponents> }): Promise<URI> {
|
||||
const payload: Partial<UriComponents> = options && options.payload ? options.payload : Object.create(null);
|
||||
|
||||
// we define the authority to be the extension ID to ensure
|
||||
// that the Uri gets routed back to the extension properly.
|
||||
payload.authority = extensionId.value;
|
||||
|
||||
return this.urlService.create(payload);
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
this.handlers.forEach(({ disposable }) => disposable.dispose());
|
||||
this.handlers.clear();
|
||||
|
||||
@@ -22,9 +22,8 @@ import { VIEWLET_ID as DEBUG } from 'vs/workbench/contrib/debug/common/debug';
|
||||
import { VIEWLET_ID as REMOTE } from 'vs/workbench/contrib/remote/common/remote.contribution';
|
||||
import { ExtensionIdentifier, IExtensionDescription } from 'vs/platform/extensions/common/extensions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction } from 'vs/workbench/browser/viewlet';
|
||||
import { ViewletRegistry, Extensions as ViewletExtensions, ViewletDescriptor, ShowViewletAction, Viewlet } from 'vs/workbench/browser/viewlet';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { ViewContainerViewlet } from 'vs/workbench/browser/parts/views/viewsViewlet';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -37,6 +36,7 @@ import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IWorkbenchActionRegistry, Extensions as ActionExtensions } from 'vs/workbench/common/actions';
|
||||
import { SyncActionDescriptor } from 'vs/platform/actions/common/actions';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
|
||||
export interface IUserFriendlyViewsContainerDescriptor {
|
||||
id: string;
|
||||
@@ -315,8 +315,24 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
viewContainer = this.viewContainersRegistry.registerViewContainer(id, true, extensionId);
|
||||
|
||||
// Register as viewlet
|
||||
class CustomViewlet extends ViewContainerViewlet {
|
||||
class CustomViewPaneContainer extends ViewPaneContainer {
|
||||
constructor(
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IWorkspaceContextService protected contextService: IWorkspaceContextService,
|
||||
@IStorageService protected storageService: IStorageService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IInstantiationService protected instantiationService: IInstantiationService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
) {
|
||||
super(id, `${id}.state`, { showHeaderInTitleWhenSingleView: true }, instantiationService, configurationService, layoutService, contextMenuService, telemetryService, extensionService, themeService, storageService, contextService);
|
||||
}
|
||||
}
|
||||
|
||||
// Register a viewlet
|
||||
class CustomViewlet extends Viewlet {
|
||||
constructor(
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@@ -329,9 +345,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
@IContextMenuService contextMenuService: IContextMenuService,
|
||||
@IExtensionService extensionService: IExtensionService
|
||||
) {
|
||||
super(id, `${id}.state`, true, configurationService, layoutService, telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService);
|
||||
super(id, instantiationService.createInstance(CustomViewPaneContainer), telemetryService, storageService, instantiationService, themeService, contextMenuService, extensionService, contextService, layoutService, configurationService);
|
||||
}
|
||||
}
|
||||
|
||||
const viewletDescriptor = ViewletDescriptor.create(
|
||||
CustomViewlet,
|
||||
id,
|
||||
|
||||
@@ -68,6 +68,7 @@ import { IURITransformerService } from 'vs/workbench/api/common/extHostUriTransf
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
|
||||
import { find } from 'vs/base/common/arrays';
|
||||
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
|
||||
export interface IExtensionApiFactory {
|
||||
(extension: IExtensionDescription, registry: ExtensionDescriptionRegistry, configProvider: ExtHostConfigProvider): typeof vscode;
|
||||
@@ -87,6 +88,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
const rpcProtocol = accessor.get(IExtHostRpcService);
|
||||
const extHostStorage = accessor.get(IExtHostStorage);
|
||||
const extHostLogService = accessor.get(ILogService);
|
||||
const extHostTunnelService = accessor.get(IExtHostTunnelService);
|
||||
|
||||
// register addressable instances
|
||||
rpcProtocol.set(ExtHostContext.ExtHostLogService, <ExtHostLogServiceShape><any>extHostLogService);
|
||||
@@ -94,6 +96,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
rpcProtocol.set(ExtHostContext.ExtHostConfiguration, extHostConfiguration);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostExtensionService, extensionService);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostStorage, extHostStorage);
|
||||
rpcProtocol.set(ExtHostContext.ExtHostTunnelService, extHostTunnelService);
|
||||
|
||||
// automatically create and register addressable instances
|
||||
const extHostDecorations = rpcProtocol.set(ExtHostContext.ExtHostDecorations, accessor.get(IExtHostDecorations));
|
||||
@@ -228,10 +231,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get appName() { return initData.environment.appName; },
|
||||
get appRoot() { return initData.environment.appRoot!.fsPath; },
|
||||
get uriScheme() { return initData.environment.appUriScheme; },
|
||||
createAppUri(options?) {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostUrls.proposedCreateAppUri(extension.identifier, options);
|
||||
},
|
||||
get logLevel() {
|
||||
checkProposedApiEnabled(extension);
|
||||
return typeConverters.LogLevel.to(extHostLogService.getLevel());
|
||||
@@ -506,7 +505,6 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
return extHostStatusBar.setStatusBarMessage(text, timeoutOrThenable);
|
||||
},
|
||||
withScmProgress<R>(task: (progress: vscode.Progress<number>) => Thenable<R>) {
|
||||
console.warn(`[Deprecation Warning] function 'withScmProgress' is deprecated and should no longer be used. Use 'withProgress' instead.`);
|
||||
return extHostProgress.withProgress(extension, { location: extHostTypes.ProgressLocation.SourceControl }, (progress, token) => task({ report(n: number) { /*noop*/ } }));
|
||||
},
|
||||
withProgress<R>(options: vscode.ProgressOptions, task: (progress: vscode.Progress<{ message?: string; worked?: number }>, token: vscode.CancellationToken) => Thenable<R>) {
|
||||
@@ -565,7 +563,7 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
get rootPath() {
|
||||
if (extension.isUnderDevelopment && !warnedRootPathDeprecated) {
|
||||
warnedRootPathDeprecated = true;
|
||||
console.warn(`[Deprecation Warning] 'workspace.rootPath' is deprecated and should no longer be used. Please use 'workspace.workspaceFolders' instead. More details: https://aka.ms/vscode-eliminating-rootpath`);
|
||||
extHostLogService.warn(`[Deprecation Warning] 'workspace.rootPath' is deprecated and should no longer be used. Please use 'workspace.workspaceFolders' instead. More details: https://aka.ms/vscode-eliminating-rootpath`);
|
||||
}
|
||||
|
||||
return extHostWorkspace.getPath();
|
||||
@@ -721,6 +719,10 @@ export function createApiFactoryAndRegisterActors(accessor: ServicesAccessor): I
|
||||
},
|
||||
onWillRenameFiles: (listener: (e: vscode.FileWillRenameEvent) => any, thisArg?: any, disposables?: vscode.Disposable[]) => {
|
||||
return extHostFileSystemEvent.getOnWillRenameFileEvent(extension)(listener, thisArg, disposables);
|
||||
},
|
||||
makeTunnel: (forward: vscode.TunnelOptions) => {
|
||||
checkProposedApiEnabled(extension);
|
||||
return extHostTunnelService.makeTunnel(forward);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -47,6 +47,7 @@ import { createExtHostContextProxyIdentifier as createExtId, createMainContextPr
|
||||
import * as search from 'vs/workbench/services/search/common/search';
|
||||
import { SaveReason } from 'vs/workbench/common/editor';
|
||||
import { ExtensionActivationReason } from 'vs/workbench/api/common/extHostExtensionActivator';
|
||||
import { TunnelOptions, TunnelDto } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
|
||||
// {{SQL CARBON EDIT}}
|
||||
import { ITreeItem as sqlITreeItem } from 'sql/workbench/common/views';
|
||||
@@ -90,6 +91,7 @@ export interface IInitData {
|
||||
telemetryInfo: ITelemetryInfo;
|
||||
logLevel: LogLevel;
|
||||
logsLocation: URI;
|
||||
logFile: URI;
|
||||
autoStart: boolean;
|
||||
remote: { isRemote: boolean; authority: string | undefined; };
|
||||
uiKind: UIKind;
|
||||
@@ -614,7 +616,6 @@ export interface MainThreadUrlsShape extends IDisposable {
|
||||
$registerUriHandler(handle: number, extensionId: ExtensionIdentifier): Promise<void>;
|
||||
$unregisterUriHandler(handle: number): Promise<void>;
|
||||
$createAppUri(uri: UriComponents): Promise<UriComponents>;
|
||||
$proposedCreateAppUri(extensionId: ExtensionIdentifier, options?: { payload?: Partial<UriComponents>; }): Promise<UriComponents>;
|
||||
}
|
||||
|
||||
export interface ExtHostUrlsShape {
|
||||
@@ -781,6 +782,12 @@ export interface MainThreadWindowShape extends IDisposable {
|
||||
$asExternalUri(uri: UriComponents, options: IOpenUriOptions): Promise<UriComponents>;
|
||||
}
|
||||
|
||||
export interface MainThreadTunnelServiceShape extends IDisposable {
|
||||
$openTunnel(tunnelOptions: TunnelOptions): Promise<TunnelDto | undefined>;
|
||||
$closeTunnel(remotePort: number): Promise<void>;
|
||||
$addDetected(tunnels: { remote: { port: number, host: string }, localAddress: string }[]): Promise<void>;
|
||||
}
|
||||
|
||||
// -- extension host
|
||||
|
||||
export interface ExtHostCommandsShape {
|
||||
@@ -1395,6 +1402,11 @@ export interface ExtHostStorageShape {
|
||||
$acceptValue(shared: boolean, key: string, value: object | undefined): void;
|
||||
}
|
||||
|
||||
|
||||
export interface ExtHostTunnelServiceShape {
|
||||
|
||||
}
|
||||
|
||||
// --- proxy identifiers
|
||||
|
||||
export const MainContext = {
|
||||
@@ -1435,7 +1447,8 @@ export const MainContext = {
|
||||
MainThreadSearch: createMainId<MainThreadSearchShape>('MainThreadSearch'),
|
||||
MainThreadTask: createMainId<MainThreadTaskShape>('MainThreadTask'),
|
||||
MainThreadWindow: createMainId<MainThreadWindowShape>('MainThreadWindow'),
|
||||
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService')
|
||||
MainThreadLabelService: createMainId<MainThreadLabelServiceShape>('MainThreadLabelService'),
|
||||
MainThreadTunnelService: createMainId<MainThreadTunnelServiceShape>('MainThreadTunnelService')
|
||||
};
|
||||
|
||||
export const ExtHostContext = {
|
||||
@@ -1469,5 +1482,6 @@ export const ExtHostContext = {
|
||||
ExtHostStorage: createMainId<ExtHostStorageShape>('ExtHostStorage'),
|
||||
ExtHostUrls: createExtId<ExtHostUrlsShape>('ExtHostUrls'),
|
||||
ExtHostOutputService: createMainId<ExtHostOutputServiceShape>('ExtHostOutputService'),
|
||||
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService')
|
||||
ExtHosLabelService: createMainId<ExtHostLabelServiceShape>('ExtHostLabelService'),
|
||||
ExtHostTunnelService: createMainId<ExtHostTunnelServiceShape>('ExtHostTunnelService')
|
||||
};
|
||||
|
||||
@@ -27,6 +27,7 @@ export class ApiCommandArgument<V, O = V> {
|
||||
|
||||
static readonly Uri = new ApiCommandArgument<URI>('uri', 'Uri of a text document', v => URI.isUri(v), v => v);
|
||||
static readonly Position = new ApiCommandArgument<types.Position, IPosition>('position', 'A position in a text document', v => types.Position.isPosition(v), typeConverters.Position.from);
|
||||
static readonly Range = new ApiCommandArgument<types.Range, IRange>('range', 'A range in a text document', v => types.Range.isRange(v), typeConverters.Range.from);
|
||||
|
||||
static readonly CallHierarchyItem = new ApiCommandArgument('item', 'A call hierarchy item', v => v instanceof types.CallHierarchyItem, typeConverters.CallHierarchyItem.to);
|
||||
|
||||
@@ -42,7 +43,7 @@ export class ApiCommandResult<V, O = V> {
|
||||
|
||||
constructor(
|
||||
readonly description: string,
|
||||
readonly convert: (v: V) => O
|
||||
readonly convert: (v: V, apiArgs: any[]) => O
|
||||
) { }
|
||||
}
|
||||
|
||||
@@ -68,7 +69,7 @@ export class ApiCommand {
|
||||
});
|
||||
|
||||
const internalResult = await commands.executeCommand(this.internalId, ...internalArgs);
|
||||
return this.result.convert(internalResult);
|
||||
return this.result.convert(internalResult, apiArgs);
|
||||
}, undefined, this._getCommandHandlerDesc());
|
||||
}
|
||||
|
||||
@@ -83,6 +84,123 @@ export class ApiCommand {
|
||||
|
||||
|
||||
const newCommands: ApiCommand[] = [
|
||||
// -- document highlights
|
||||
new ApiCommand(
|
||||
'vscode.executeDocumentHighlights', '_executeDocumentHighlights', 'Execute document highlight provider.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
new ApiCommandResult<modes.DocumentHighlight[], types.DocumentHighlight[] | undefined>('A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.', tryMapWith(typeConverters.DocumentHighlight.to))
|
||||
),
|
||||
// -- document symbols
|
||||
new ApiCommand(
|
||||
'vscode.executeDocumentSymbolProvider', '_executeDocumentSymbolProvider', 'Execute document symbol provider.',
|
||||
[ApiCommandArgument.Uri],
|
||||
new ApiCommandResult<modes.DocumentSymbol[], vscode.SymbolInformation[] | undefined>('A promise that resolves to an array of DocumentHighlight-instances.', (value, apiArgs) => {
|
||||
|
||||
if (isFalsyOrEmpty(value)) {
|
||||
return undefined;
|
||||
}
|
||||
class MergedInfo extends types.SymbolInformation implements vscode.DocumentSymbol {
|
||||
static to(symbol: modes.DocumentSymbol): MergedInfo {
|
||||
const res = new MergedInfo(
|
||||
symbol.name,
|
||||
typeConverters.SymbolKind.to(symbol.kind),
|
||||
symbol.containerName || '',
|
||||
new types.Location(apiArgs[0], typeConverters.Range.to(symbol.range))
|
||||
);
|
||||
res.detail = symbol.detail;
|
||||
res.range = res.location.range;
|
||||
res.selectionRange = typeConverters.Range.to(symbol.selectionRange);
|
||||
res.children = symbol.children ? symbol.children.map(MergedInfo.to) : [];
|
||||
return res;
|
||||
}
|
||||
|
||||
detail!: string;
|
||||
range!: vscode.Range;
|
||||
selectionRange!: vscode.Range;
|
||||
children!: vscode.DocumentSymbol[];
|
||||
containerName!: string;
|
||||
}
|
||||
return value.map(MergedInfo.to);
|
||||
|
||||
})
|
||||
),
|
||||
// -- formatting
|
||||
new ApiCommand(
|
||||
'vscode.executeFormatDocumentProvider', '_executeFormatDocumentProvider', 'Execute document format provider.',
|
||||
[ApiCommandArgument.Uri, new ApiCommandArgument('options', 'Formatting options', _ => true, v => v)],
|
||||
new ApiCommandResult<ISingleEditOperation[], types.TextEdit[] | undefined>('A promise that resolves to an array of TextEdits.', tryMapWith(typeConverters.TextEdit.to))
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.executeFormatRangeProvider', '_executeFormatRangeProvider', 'Execute range format provider.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Range, new ApiCommandArgument('options', 'Formatting options', _ => true, v => v)],
|
||||
new ApiCommandResult<ISingleEditOperation[], types.TextEdit[] | undefined>('A promise that resolves to an array of TextEdits.', tryMapWith(typeConverters.TextEdit.to))
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.executeFormatOnTypeProvider', '_executeFormatOnTypeProvider', 'Execute format on type provider.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position, new ApiCommandArgument('ch', 'Trigger character', v => typeof v === 'string', v => v), new ApiCommandArgument('options', 'Formatting options', _ => true, v => v)],
|
||||
new ApiCommandResult<ISingleEditOperation[], types.TextEdit[] | undefined>('A promise that resolves to an array of TextEdits.', tryMapWith(typeConverters.TextEdit.to))
|
||||
),
|
||||
// -- go to symbol (definition, type definition, declaration, impl, references)
|
||||
new ApiCommand(
|
||||
'vscode.executeDefinitionProvider', '_executeDefinitionProvider', 'Execute all definition provider.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.executeTypeDefinitionProvider', '_executeTypeDefinitionProvider', 'Execute all type definition providers.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.executeDeclarationProvider', '_executeDeclarationProvider', 'Execute all declaration providers.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.executeImplementationProvider', '_executeImplementationProvider', 'Execute all implementation providers.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
|
||||
),
|
||||
new ApiCommand(
|
||||
'vscode.executeReferenceProvider', '_executeReferenceProvider', 'Execute all reference providers.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
new ApiCommandResult<modes.Location[], types.Location[] | undefined>('A promise that resolves to an array of Location-instances.', tryMapWith(typeConverters.location.to))
|
||||
),
|
||||
// -- hover
|
||||
new ApiCommand(
|
||||
'vscode.executeHoverProvider', '_executeHoverProvider', 'Execute all hover provider.',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
new ApiCommandResult<modes.Hover[], types.Hover[] | undefined>('A promise that resolves to an array of Hover-instances.', tryMapWith(typeConverters.Hover.to))
|
||||
),
|
||||
// -- selection range
|
||||
new ApiCommand(
|
||||
'vscode.executeSelectionRangeProvider', '_executeSelectionRangeProvider', 'Execute selection range provider.',
|
||||
[ApiCommandArgument.Uri, new ApiCommandArgument('position', 'A positions in a text document', v => Array.isArray(v) && v.every(v => types.Position.isPosition(v)), v => v.map(typeConverters.Position.from))],
|
||||
new ApiCommandResult<IRange[][], types.SelectionRange[]>('A promise that resolves to an array of ranges.', result => {
|
||||
return result.map(ranges => {
|
||||
let node: types.SelectionRange | undefined;
|
||||
for (const range of ranges.reverse()) {
|
||||
node = new types.SelectionRange(typeConverters.Range.to(range), node);
|
||||
}
|
||||
return node!;
|
||||
});
|
||||
})
|
||||
),
|
||||
// -- symbol search
|
||||
new ApiCommand(
|
||||
'vscode.executeWorkspaceSymbolProvider', '_executeWorkspaceSymbolProvider', 'Execute all workspace symbol provider.',
|
||||
[new ApiCommandArgument('query', 'Search string', v => typeof v === 'string', v => v)],
|
||||
new ApiCommandResult<[search.IWorkspaceSymbolProvider, search.IWorkspaceSymbol[]][], types.SymbolInformation[]>('A promise that resolves to an array of SymbolInformation-instances.', value => {
|
||||
const result: types.SymbolInformation[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (let tuple of value) {
|
||||
result.push(...tuple[1].map(typeConverters.WorkspaceSymbol.to));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
})
|
||||
),
|
||||
// --- call hierarchy
|
||||
new ApiCommand(
|
||||
'vscode.prepareCallHierarchy', '_executePrepareCallHierarchy', 'Prepare call hierarchy at a position inside a document',
|
||||
[ApiCommandArgument.Uri, ApiCommandArgument.Position],
|
||||
@@ -121,68 +239,6 @@ export class ExtHostApiCommands {
|
||||
}
|
||||
|
||||
registerCommands() {
|
||||
this._register('vscode.executeWorkspaceSymbolProvider', this._executeWorkspaceSymbolProvider, {
|
||||
description: 'Execute all workspace symbol provider.',
|
||||
args: [{ name: 'query', description: 'Search string', constraint: String }],
|
||||
returns: 'A promise that resolves to an array of SymbolInformation-instances.'
|
||||
|
||||
});
|
||||
this._register('vscode.executeDefinitionProvider', this._executeDefinitionProvider, {
|
||||
description: 'Execute all definition provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeDeclarationProvider', this._executeDeclaraionProvider, {
|
||||
description: 'Execute all declaration provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeTypeDefinitionProvider', this._executeTypeDefinitionProvider, {
|
||||
description: 'Execute all type definition providers.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeImplementationProvider', this._executeImplementationProvider, {
|
||||
description: 'Execute all implementation providers.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instance.'
|
||||
});
|
||||
this._register('vscode.executeHoverProvider', this._executeHoverProvider, {
|
||||
description: 'Execute all hover provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position of a symbol', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Hover-instances.'
|
||||
});
|
||||
this._register('vscode.executeDocumentHighlights', this._executeDocumentHighlights, {
|
||||
description: 'Execute document highlight provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of DocumentHighlight-instances.'
|
||||
});
|
||||
this._register('vscode.executeReferenceProvider', this._executeReferenceProvider, {
|
||||
description: 'Execute reference provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of Location-instances.'
|
||||
});
|
||||
this._register('vscode.executeDocumentRenameProvider', this._executeDocumentRenameProvider, {
|
||||
description: 'Execute rename provider.',
|
||||
args: [
|
||||
@@ -201,13 +257,6 @@ export class ExtHostApiCommands {
|
||||
],
|
||||
returns: 'A promise that resolves to SignatureHelp.'
|
||||
});
|
||||
this._register('vscode.executeDocumentSymbolProvider', this._executeDocumentSymbolProvider, {
|
||||
description: 'Execute document symbol provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of SymbolInformation and DocumentSymbol instances.'
|
||||
});
|
||||
this._register('vscode.executeCompletionItemProvider', this._executeCompletionItemProvider, {
|
||||
description: 'Execute completion item provider.',
|
||||
args: [
|
||||
@@ -235,33 +284,7 @@ export class ExtHostApiCommands {
|
||||
],
|
||||
returns: 'A promise that resolves to an array of CodeLens-instances.'
|
||||
});
|
||||
this._register('vscode.executeFormatDocumentProvider', this._executeFormatDocumentProvider, {
|
||||
description: 'Execute document format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeFormatRangeProvider', this._executeFormatRangeProvider, {
|
||||
description: 'Execute range format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'range', description: 'Range in a text document', constraint: types.Range },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
this._register('vscode.executeFormatOnTypeProvider', this._executeFormatOnTypeProvider, {
|
||||
description: 'Execute document format provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'position', description: 'Position in a text document', constraint: types.Position },
|
||||
{ name: 'ch', description: 'Character that got typed', constraint: String },
|
||||
{ name: 'options', description: 'Formatting options' }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of TextEdits.'
|
||||
});
|
||||
|
||||
this._register('vscode.executeLinkProvider', this._executeDocumentLinkProvider, {
|
||||
description: 'Execute document link provider.',
|
||||
args: [
|
||||
@@ -284,14 +307,6 @@ export class ExtHostApiCommands {
|
||||
],
|
||||
returns: 'A promise that resolves to an array of ColorPresentation objects.'
|
||||
});
|
||||
this._register('vscode.executeSelectionRangeProvider', this._executeSelectionRangeProvider, {
|
||||
description: 'Execute selection range provider.',
|
||||
args: [
|
||||
{ name: 'uri', description: 'Uri of a text document', constraint: URI },
|
||||
{ name: 'positions', description: 'Positions in a text document', constraint: Array.isArray }
|
||||
],
|
||||
returns: 'A promise that resolves to an array of ranges.'
|
||||
});
|
||||
|
||||
// -----------------------------------------------------------------
|
||||
// The following commands are registered on both sides separately.
|
||||
@@ -362,87 +377,6 @@ export class ExtHostApiCommands {
|
||||
this._disposables.add(disposable);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute workspace symbol provider.
|
||||
*
|
||||
* @param query Search string to match query symbol names
|
||||
* @return A promise that resolves to an array of symbol information.
|
||||
*/
|
||||
private _executeWorkspaceSymbolProvider(query: string): Promise<types.SymbolInformation[]> {
|
||||
return this._commands.executeCommand<[search.IWorkspaceSymbolProvider, search.IWorkspaceSymbol[]][]>('_executeWorkspaceSymbolProvider', { query }).then(value => {
|
||||
const result: types.SymbolInformation[] = [];
|
||||
if (Array.isArray(value)) {
|
||||
for (let tuple of value) {
|
||||
result.push(...tuple[1].map(typeConverters.WorkspaceSymbol.to));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDefinitionProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeDefinitionProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeDeclaraionProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeDeclarationProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeTypeDefinitionProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeTypeDefinitionProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeImplementationProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeImplementationProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeHoverProvider(resource: URI, position: types.Position): Promise<types.Hover[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Hover[]>('_executeHoverProvider', args)
|
||||
.then(tryMapWith(typeConverters.Hover.to));
|
||||
}
|
||||
|
||||
private _executeDocumentHighlights(resource: URI, position: types.Position): Promise<types.DocumentHighlight[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.DocumentHighlight[]>('_executeDocumentHighlights', args)
|
||||
.then(tryMapWith(typeConverters.DocumentHighlight.to));
|
||||
}
|
||||
|
||||
private _executeReferenceProvider(resource: URI, position: types.Position): Promise<types.Location[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: position && typeConverters.Position.from(position)
|
||||
};
|
||||
return this._commands.executeCommand<modes.Location[]>('_executeReferenceProvider', args)
|
||||
.then(tryMapWith(typeConverters.location.to));
|
||||
}
|
||||
|
||||
private _executeDocumentRenameProvider(resource: URI, position: types.Position, newName: string): Promise<types.WorkspaceEdit> {
|
||||
const args = {
|
||||
resource,
|
||||
@@ -502,24 +436,6 @@ export class ExtHostApiCommands {
|
||||
});
|
||||
}
|
||||
|
||||
private _executeSelectionRangeProvider(resource: URI, positions: types.Position[]): Promise<vscode.SelectionRange[]> {
|
||||
const pos = positions.map(typeConverters.Position.from);
|
||||
const args = {
|
||||
resource,
|
||||
position: pos[0],
|
||||
positions: pos
|
||||
};
|
||||
return this._commands.executeCommand<IRange[][]>('_executeSelectionRangeProvider', args).then(result => {
|
||||
return result.map(ranges => {
|
||||
let node: types.SelectionRange | undefined;
|
||||
for (const range of ranges.reverse()) {
|
||||
node = new types.SelectionRange(typeConverters.Range.to(range), node);
|
||||
}
|
||||
return node!;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private _executeColorPresentationProvider(color: types.Color, context: { uri: URI, range: types.Range; }): Promise<types.ColorPresentation[]> {
|
||||
const args = {
|
||||
resource: context.uri,
|
||||
@@ -534,38 +450,6 @@ export class ExtHostApiCommands {
|
||||
});
|
||||
}
|
||||
|
||||
private _executeDocumentSymbolProvider(resource: URI): Promise<vscode.SymbolInformation[] | undefined> {
|
||||
const args = {
|
||||
resource
|
||||
};
|
||||
return this._commands.executeCommand<modes.DocumentSymbol[]>('_executeDocumentSymbolProvider', args).then((value): vscode.SymbolInformation[] | undefined => {
|
||||
if (isFalsyOrEmpty(value)) {
|
||||
return undefined;
|
||||
}
|
||||
class MergedInfo extends types.SymbolInformation implements vscode.DocumentSymbol {
|
||||
static to(symbol: modes.DocumentSymbol): MergedInfo {
|
||||
const res = new MergedInfo(
|
||||
symbol.name,
|
||||
typeConverters.SymbolKind.to(symbol.kind),
|
||||
symbol.containerName || '',
|
||||
new types.Location(resource, typeConverters.Range.to(symbol.range))
|
||||
);
|
||||
res.detail = symbol.detail;
|
||||
res.range = res.location.range;
|
||||
res.selectionRange = typeConverters.Range.to(symbol.selectionRange);
|
||||
res.children = symbol.children ? symbol.children.map(MergedInfo.to) : [];
|
||||
return res;
|
||||
}
|
||||
|
||||
detail!: string;
|
||||
range!: vscode.Range;
|
||||
selectionRange!: vscode.Range;
|
||||
children!: vscode.DocumentSymbol[];
|
||||
containerName!: string;
|
||||
}
|
||||
return value.map(MergedInfo.to);
|
||||
});
|
||||
}
|
||||
|
||||
private _executeCodeActionProvider(resource: URI, rangeOrSelection: types.Range | types.Selection, kind?: string): Promise<(vscode.CodeAction | vscode.Command | undefined)[] | undefined> {
|
||||
const args = {
|
||||
@@ -610,36 +494,6 @@ export class ExtHostApiCommands {
|
||||
|
||||
}
|
||||
|
||||
private _executeFormatDocumentProvider(resource: URI, options: vscode.FormattingOptions): Promise<vscode.TextEdit[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatDocumentProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeFormatRangeProvider(resource: URI, range: types.Range, options: vscode.FormattingOptions): Promise<vscode.TextEdit[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
range: typeConverters.Range.from(range),
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatRangeProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeFormatOnTypeProvider(resource: URI, position: types.Position, ch: string, options: vscode.FormattingOptions): Promise<vscode.TextEdit[] | undefined> {
|
||||
const args = {
|
||||
resource,
|
||||
position: typeConverters.Position.from(position),
|
||||
ch,
|
||||
options
|
||||
};
|
||||
return this._commands.executeCommand<ISingleEditOperation[]>('_executeFormatOnTypeProvider', args)
|
||||
.then(tryMapWith(edit => new types.TextEdit(typeConverters.Range.to(edit.range), edit.text)));
|
||||
}
|
||||
|
||||
private _executeDocumentLinkProvider(resource: URI): Promise<vscode.DocumentLink[] | undefined> {
|
||||
return this._commands.executeCommand<modes.ILink[]>('_executeLinkProvider', resource)
|
||||
.then(tryMapWith(typeConverters.DocumentLink.to));
|
||||
|
||||
@@ -13,6 +13,7 @@ import { ExtHostDocumentData } from 'vs/workbench/api/common/extHostDocumentData
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { ExtHostTextEditor } from 'vs/workbench/api/common/extHostTextEditor';
|
||||
import * as typeConverters from 'vs/workbench/api/common/extHostTypeConverters';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsShape {
|
||||
|
||||
@@ -35,6 +36,7 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
|
||||
|
||||
constructor(
|
||||
@IExtHostRpcService private readonly _extHostRpc: IExtHostRpcService,
|
||||
@ILogService private readonly _logService: ILogService
|
||||
) { }
|
||||
|
||||
$acceptDocumentsAndEditorsDelta(delta: IDocumentsAndEditorsDelta): void {
|
||||
@@ -92,8 +94,9 @@ export class ExtHostDocumentsAndEditors implements ExtHostDocumentsAndEditorsSha
|
||||
|
||||
const documentData = this._documents.get(resource.toString())!;
|
||||
const editor = new ExtHostTextEditor(
|
||||
this._extHostRpc.getProxy(MainContext.MainThreadTextEditors),
|
||||
data.id,
|
||||
this._extHostRpc.getProxy(MainContext.MainThreadTextEditors),
|
||||
this._logService,
|
||||
documentData,
|
||||
data.selections.map(typeConverters.Selection.to),
|
||||
data.options,
|
||||
|
||||
@@ -32,6 +32,7 @@ import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitData
|
||||
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
|
||||
import { IExtHostRpcService } from 'vs/workbench/api/common/extHostRpcService';
|
||||
import { ServiceCollection } from 'vs/platform/instantiation/common/serviceCollection';
|
||||
import { IExtHostTunnelService } from 'vs/workbench/api/common/extHostTunnelService';
|
||||
|
||||
interface ITestRunner {
|
||||
/** Old test runner API, as exported from `vscode/lib/testrunner` */
|
||||
@@ -76,6 +77,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
protected readonly _extHostWorkspace: ExtHostWorkspace;
|
||||
protected readonly _extHostConfiguration: ExtHostConfiguration;
|
||||
protected readonly _logService: ILogService;
|
||||
protected readonly _extHostTunnelService: IExtHostTunnelService;
|
||||
|
||||
protected readonly _mainThreadWorkspaceProxy: MainThreadWorkspaceShape;
|
||||
protected readonly _mainThreadTelemetryProxy: MainThreadTelemetryShape;
|
||||
@@ -104,7 +106,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
@IExtHostConfiguration extHostConfiguration: IExtHostConfiguration,
|
||||
@ILogService logService: ILogService,
|
||||
@IExtHostInitDataService initData: IExtHostInitDataService,
|
||||
@IExtensionStoragePaths storagePath: IExtensionStoragePaths
|
||||
@IExtensionStoragePaths storagePath: IExtensionStoragePaths,
|
||||
@IExtHostTunnelService extHostTunnelService: IExtHostTunnelService
|
||||
) {
|
||||
this._hostUtils = hostUtils;
|
||||
this._extHostContext = extHostContext;
|
||||
@@ -113,6 +116,7 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
this._extHostWorkspace = extHostWorkspace;
|
||||
this._extHostConfiguration = extHostConfiguration;
|
||||
this._logService = logService;
|
||||
this._extHostTunnelService = extHostTunnelService;
|
||||
this._disposables = new DisposableStore();
|
||||
|
||||
this._mainThreadWorkspaceProxy = this._extHostContext.getProxy(MainContext.MainThreadWorkspace);
|
||||
@@ -652,6 +656,8 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
|
||||
extensionHostEnv: result.extensionHostEnv
|
||||
};
|
||||
|
||||
await this._extHostTunnelService.addDetected(result.detectedTunnels);
|
||||
|
||||
return {
|
||||
type: 'ok',
|
||||
value: {
|
||||
|
||||
@@ -389,7 +389,7 @@ class CodeActionAdapter {
|
||||
edit: candidate.edit && typeConvert.WorkspaceEdit.from(candidate.edit),
|
||||
kind: candidate.kind && candidate.kind.value,
|
||||
isPreferred: candidate.isPreferred,
|
||||
disabled: candidate.disabled
|
||||
disabled: candidate.disabled?.reason
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -740,11 +740,16 @@ class SuggestAdapter {
|
||||
private _cache = new Cache<vscode.CompletionItem>('CompletionItem');
|
||||
private _disposables = new Map<number, DisposableStore>();
|
||||
|
||||
private _didWarnMust: boolean = false;
|
||||
private _didWarnShould: boolean = false;
|
||||
|
||||
constructor(
|
||||
private readonly _documents: ExtHostDocuments,
|
||||
private readonly _commands: CommandsConverter,
|
||||
private readonly _provider: vscode.CompletionItemProvider,
|
||||
private readonly _logService: ILogService
|
||||
private readonly _logService: ILogService,
|
||||
private readonly _telemetry: extHostProtocol.MainThreadTelemetryShape,
|
||||
private readonly _extensionId: ExtensionIdentifier
|
||||
) { }
|
||||
|
||||
provideCompletionItems(resource: URI, position: IPosition, context: modes.CompletionContext, token: CancellationToken): Promise<extHostProtocol.ISuggestResultDto | undefined> {
|
||||
@@ -809,12 +814,37 @@ class SuggestAdapter {
|
||||
return Promise.resolve(undefined);
|
||||
}
|
||||
|
||||
const _mustNotChange = SuggestAdapter._mustNotChangeHash(item);
|
||||
const _mayNotChange = SuggestAdapter._mayNotChangeHash(item);
|
||||
|
||||
return asPromise(() => this._provider.resolveCompletionItem!(item, token)).then(resolvedItem => {
|
||||
|
||||
if (!resolvedItem) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
type BlameExtension = {
|
||||
extensionId: string;
|
||||
kind: string
|
||||
};
|
||||
|
||||
type BlameExtensionMeta = {
|
||||
extensionId: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
kind: { classification: 'SystemMetaData', purpose: 'PerformanceAndHealth' };
|
||||
};
|
||||
|
||||
if (!this._didWarnMust && _mustNotChange !== SuggestAdapter._mustNotChangeHash(resolvedItem)) {
|
||||
this._logService.warn(`[${this._extensionId.value}] INVALID result from 'resolveCompletionItem', extension MUST NOT change any of: label, sortText, filterText, insertText, or textEdit`);
|
||||
this._telemetry.$publicLog2<BlameExtension, BlameExtensionMeta>('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'must' });
|
||||
this._didWarnMust = true;
|
||||
}
|
||||
|
||||
if (!this._didWarnShould && _mayNotChange !== SuggestAdapter._mayNotChangeHash(resolvedItem)) {
|
||||
this._logService.info(`[${this._extensionId.value}] UNSAVE result from 'resolveCompletionItem', extension SHOULD NOT change any of: additionalTextEdits, or command`);
|
||||
this._telemetry.$publicLog2<BlameExtension, BlameExtensionMeta>('resolveCompletionItem/invalid', { extensionId: this._extensionId.value, kind: 'should' });
|
||||
this._didWarnShould = true;
|
||||
}
|
||||
|
||||
const pos = typeConvert.Position.to(position);
|
||||
return this._convertCompletionItem(resolvedItem, pos, id);
|
||||
});
|
||||
@@ -908,6 +938,16 @@ class SuggestAdapter {
|
||||
private static _isValidRangeForCompletion(range: vscode.Range, position: vscode.Position): boolean {
|
||||
return range.isSingleLine || range.start.line === position.line;
|
||||
}
|
||||
|
||||
private static _mustNotChangeHash(item: vscode.CompletionItem) {
|
||||
const args = [item.label, item.sortText, item.filterText, item.insertText, item.range, item.range2];
|
||||
const res = JSON.stringify(args);
|
||||
return res;
|
||||
}
|
||||
|
||||
private static _mayNotChangeHash(item: vscode.CompletionItem) {
|
||||
return JSON.stringify([item.additionalTextEdits, item.command]);
|
||||
}
|
||||
}
|
||||
|
||||
class SignatureHelpAdapter {
|
||||
@@ -1253,7 +1293,8 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
private static _handlePool: number = 0;
|
||||
|
||||
private readonly _uriTransformer: IURITransformer | null;
|
||||
private _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
|
||||
private readonly _proxy: extHostProtocol.MainThreadLanguageFeaturesShape;
|
||||
private readonly _telemetryShape: extHostProtocol.MainThreadTelemetryShape;
|
||||
private _documents: ExtHostDocuments;
|
||||
private _commands: ExtHostCommands;
|
||||
private _diagnostics: ExtHostDiagnostics;
|
||||
@@ -1270,6 +1311,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
) {
|
||||
this._uriTransformer = uriTransformer;
|
||||
this._proxy = mainContext.getProxy(extHostProtocol.MainContext.MainThreadLanguageFeatures);
|
||||
this._telemetryShape = mainContext.getProxy(extHostProtocol.MainContext.MainThreadTelemetry);
|
||||
this._documents = documents;
|
||||
this._commands = commands;
|
||||
this._diagnostics = diagnostics;
|
||||
@@ -1584,7 +1626,7 @@ export class ExtHostLanguageFeatures implements extHostProtocol.ExtHostLanguageF
|
||||
// --- suggestion
|
||||
|
||||
registerCompletionItemProvider(extension: IExtensionDescription, selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable {
|
||||
const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService), extension);
|
||||
const handle = this._addNewAdapter(new SuggestAdapter(this._documents, this._commands.converter, provider, this._logService, this._telemetryShape, extension.identifier), extension);
|
||||
this._proxy.$registerSuggestSupport(handle, this._transformDocumentSelector(selector), triggerCharacters, SuggestAdapter.supportsResolving(provider), extension.identifier);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user