Merge from vscode e6a45f4242ebddb7aa9a229f85555e8a3bd987e2 (#9253)

* Merge from vscode e6a45f4242ebddb7aa9a229f85555e8a3bd987e2

* skip failing tests

* remove github-authentication extensions

* ignore github compile steps

* ignore github compile steps

* check in compiled files
This commit is contained in:
Anthony Dresser
2020-02-21 12:11:51 -08:00
committed by GitHub
parent c74bac3746
commit 1b78a9b1e0
179 changed files with 3200 additions and 1830 deletions
@@ -116,6 +116,11 @@ steps:
yarn gulp minify-vscode-reh-web
displayName: Compile
condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true'))
env:
OSS_GITHUB_ID: "a5d3c261b032765a78de"
OSS_GITHUB_SECRET: $(oss-github-client-secret)
INSIDERS_GITHUB_ID: "31f02627809389d9f111"
INSIDERS_GITHUB_SECRET: $(insiders-github-client-secret)
- script: |
set -e
+13
View File
@@ -74,6 +74,7 @@ function compileTask(src, out, build) {
if (src === 'src') {
generator.execute();
}
// generateGitHubAuthConfig();
return srcPipe
.pipe(generator.stream)
.pipe(compile())
@@ -96,6 +97,18 @@ function watchTask(out, build) {
}
exports.watchTask = watchTask;
const REPO_SRC_FOLDER = path.join(__dirname, '../../src');
/*function generateGitHubAuthConfig() {
const schemes = ['OSS', 'INSIDERS'];
let content: { [key: string]: { id?: string, secret?: string }} = {};
schemes.forEach(scheme => {
content[scheme] = {
id: process.env[`${scheme}_GITHUB_ID`],
secret: process.env[`${scheme}_GITHUB_SECRET`]
};
});
fs.writeFileSync(path.join(__dirname, '../../extensions/github-authentication/src/common/config.json'), JSON.stringify(content));
}*/
class MonacoGenerator {
constructor(isWatch) {
this._executeSoonTimer = null;
+15
View File
@@ -88,6 +88,8 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod
generator.execute();
}
// generateGitHubAuthConfig();
return srcPipe
.pipe(generator.stream)
.pipe(compile())
@@ -115,6 +117,19 @@ export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteSt
const REPO_SRC_FOLDER = path.join(__dirname, '../../src');
/*function generateGitHubAuthConfig() {
const schemes = ['OSS', 'INSIDERS'];
let content: { [key: string]: { id?: string, secret?: string }} = {};
schemes.forEach(scheme => {
content[scheme] = {
id: process.env[`${scheme}_GITHUB_ID`],
secret: process.env[`${scheme}_GITHUB_SECRET`]
};
});
fs.writeFileSync(path.join(__dirname, '../../extensions/github-authentication/src/common/config.json'), JSON.stringify(content));
}*/
class MonacoGenerator {
private readonly _isWatch: boolean;
public readonly stream: NodeJS.ReadWriteStream;
+4
View File
@@ -302,6 +302,10 @@
"name": "vs/workbench/services/textMate",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/workingCopy",
"project": "vscode-workbench"
},
{
"name": "vs/workbench/services/workspaces",
"project": "vscode-workbench"
+1 -1
View File
@@ -232,7 +232,7 @@ function onUnexpectedError(error, enableDeveloperTools) {
console.error('[uncaught exception]: ' + error);
if (error.stack) {
if (error && error.stack) {
console.error(error.stack);
}
}
@@ -15,7 +15,7 @@ export class AdditionalKeyBindings<T> implements Slick.Plugin<T> {
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.handler.subscribe(this.grid.onKeyDown, (e: KeyboardEvent, args) => this.handleKeyDown(e, args));
this.handler.subscribe(this.grid.onKeyDown, (e: DOMEvent, args) => this.handleKeyDown(e as KeyboardEvent, args));
}
public destroy() {
@@ -59,9 +59,9 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
this.canvas = this.grid.getCanvasNode();
this.handler
.subscribe(this.grid.onDragInit, e => this.handleDragInit(e))
.subscribe(this.grid.onDragStart, (e: MouseEvent, dd) => this.handleDragStart(e, dd))
.subscribe(this.grid.onDrag, (e: MouseEvent, dd) => this.handleDrag(e, dd))
.subscribe(this.grid.onDragEnd, (e: MouseEvent, dd) => this.handleDragEnd(e, dd));
.subscribe(this.grid.onDragStart, (e: DOMEvent, dd) => this.handleDragStart(e as MouseEvent, dd))
.subscribe(this.grid.onDrag, (e: DOMEvent, dd) => this.handleDrag(e as MouseEvent, dd))
.subscribe(this.grid.onDragEnd, (e: DOMEvent, dd) => this.handleDragEnd(e as MouseEvent, dd));
}
public destroy() {
@@ -36,10 +36,10 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, args));
this._handler.subscribe(this.grid.onKeyDown, (e: KeyboardEvent) => this.handleKeyDown(e));
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleIndividualCellSelection(e, args));
this._handler.subscribe(this.grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
this._handler.subscribe(this.grid.onClick, (e: DOMEvent, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e as MouseEvent, args));
this._handler.subscribe(this.grid.onKeyDown, (e: DOMEvent) => this.handleKeyDown(e as KeyboardEvent));
this._handler.subscribe(this.grid.onClick, (e: DOMEvent, args: Slick.OnClickEventArgs<T>) => this.handleIndividualCellSelection(e as MouseEvent, args));
this._handler.subscribe(this.grid.onHeaderClick, (e: DOMEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e as MouseEvent, args));
this.grid.registerPlugin(this.selector);
this._handler.subscribe(this.selector.onCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, false));
this._handler.subscribe(this.selector.onAppendCellRangeSelected, (e: Event, range: Slick.Range) => this.handleCellRangeSelected(e, range, true));
@@ -66,9 +66,9 @@ export class CheckboxSelectColumn<T extends Slick.SlickData> implements Slick.Pl
this._grid = grid;
this._handler
.subscribe(this._grid.onSelectedRowsChanged, (e: Event, args: Slick.OnSelectedRowsChangedEventArgs<T>) => this.handleSelectedRowsChanged(e, args))
.subscribe(this._grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(e, args))
.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args))
.subscribe(this._grid.onKeyDown, (e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>) => this.handleKeyDown(e, args));
.subscribe(this._grid.onClick, (e: DOMEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(e as MouseEvent, args))
.subscribe(this._grid.onHeaderClick, (e: DOMEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e as MouseEvent, args))
.subscribe(this._grid.onKeyDown, (e: DOMEvent, args: Slick.OnKeyDownEventArgs<T>) => this.handleKeyDown(e as KeyboardEvent, args));
}
public destroy(): void {
@@ -20,7 +20,7 @@ export class CopyKeybind<T> implements Slick.Plugin<T> {
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.handler.subscribe(this.grid.onKeyDown, (e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>) => this.handleKeyDown(e, args));
this.handler.subscribe(this.grid.onKeyDown, (e: DOMEvent, args: Slick.OnKeyDownEventArgs<T>) => this.handleKeyDown(e as KeyboardEvent, args));
}
public destroy() {
@@ -36,9 +36,9 @@ export class HeaderFilter<T extends Slick.SlickData> {
this.grid = grid;
this.handler.subscribe(this.grid.onHeaderCellRendered, (e: Event, args: Slick.OnHeaderCellRenderedEventArgs<T>) => this.handleHeaderCellRendered(e, args))
.subscribe(this.grid.onBeforeHeaderCellDestroy, (e: Event, args: Slick.OnBeforeHeaderCellDestroyEventArgs<T>) => this.handleBeforeHeaderCellDestroy(e, args))
.subscribe(this.grid.onClick, (e: MouseEvent) => this.handleBodyMouseDown(e))
.subscribe(this.grid.onClick, (e: DOMEvent) => this.handleBodyMouseDown(e as MouseEvent))
.subscribe(this.grid.onColumnsResized, () => this.columnsResized())
.subscribe(this.grid.onKeyDown, (e: KeyboardEvent) => this.handleKeyDown(e));
.subscribe(this.grid.onKeyDown, (e: DOMEvent) => this.handleKeyDown(e as KeyboardEvent));
this.grid.setColumns(this.grid.getColumns());
this.disposableStore.add(addDisposableListener(document.body, 'mousedown', e => this.handleBodyMouseDown(e)));
@@ -71,7 +71,7 @@ export class RowDetailView<T extends Slick.SlickData> {
this._grid.getOptions().minRowBuffer = this._options.panelRows + 3;
this._handler
.subscribe(this._grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(e, args))
.subscribe(this._grid.onClick, (e: DOMEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(e as MouseEvent, args))
.subscribe(this._grid.onSort, () => this.handleSort())
.subscribe(this._grid.onScroll, () => this.handleScroll());
@@ -18,8 +18,8 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
public init(grid: Slick.Grid<T>) {
this.grid = grid;
this.handler
.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(e, args))
.subscribe(this.grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args));
.subscribe(this.grid.onClick, (e: DOMEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(e as MouseEvent, args))
.subscribe(this.grid.onHeaderClick, (e: DOMEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e as MouseEvent, args));
}
public destroy() {
@@ -26,8 +26,8 @@ export class RowSelectionModel<T extends Slick.SlickData> implements Slick.Selec
this._grid = grid;
this._handler
.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data))
.subscribe(this._grid.onKeyDown, (e: KeyboardEvent) => this.handleKeyDown(e))
.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e));
.subscribe(this._grid.onKeyDown, (e: DOMEvent) => this.handleKeyDown(e as KeyboardEvent))
.subscribe(this._grid.onClick, (e: DOMEvent) => this.handleClick(e as MouseEvent));
}
private rangesToRows(ranges: Slick.Range[]): number[] {
+2 -2
View File
@@ -127,8 +127,8 @@ export class Table<T extends Slick.SlickData> extends Widget implements IDisposa
}
private mapMouseEvent(slickEvent: Slick.Event<any>, emitter: Emitter<ITableMouseEvent>) {
slickEvent.subscribe((e: JQuery.Event) => {
const originalEvent = e.originalEvent;
slickEvent.subscribe((e: Slick.EventData) => {
const originalEvent = (e as JQuery.Event).originalEvent;
const cell = this._grid.getCellFromEvent(originalEvent);
const anchor = originalEvent instanceof MouseEvent ? { x: originalEvent.x, y: originalEvent.y } : originalEvent.srcElement as HTMLElement;
emitter.fire({ anchor, cell });
+3 -3
View File
@@ -8,8 +8,8 @@ import 'vs/css!./media/icons';
import { ActionBar } from './actionbar';
import { Action, IActionRunner, IAction } from 'vs/base/common/actions';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar';
import { IActionRunner, IAction } from 'vs/base/common/actions';
import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IToolBarOptions } from 'vs/base/browser/ui/toolbar/toolbar';
/**
@@ -43,7 +43,7 @@ export class Taskbar {
this.actionBar = new ActionBar(element, {
orientation: options.orientation,
ariaLabel: options.ariaLabel,
actionViewItemProvider: (action: Action) => {
actionViewItemProvider: (action: IAction): IActionViewItem | undefined => {
return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined;
}
});
@@ -5,7 +5,7 @@
import * as assert from 'assert';
import * as azdata from 'azdata';
import { ICapabilitiesService, ProviderFeatures } from 'sql/platform/capabilities/common/capabilitiesService';
import { ProviderFeatures } from 'sql/platform/capabilities/common/capabilitiesService';
import { ConnectionConfig, ISaveGroupResult } from 'sql/platform/connection/common/connectionConfig';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup';
@@ -19,7 +19,7 @@ import { ConfigurationTarget } from 'vs/platform/configuration/common/configurat
import { find } from 'vs/base/common/arrays';
suite('ConnectionConfig', () => {
let capabilitiesService: TypeMoq.Mock<ICapabilitiesService>;
let capabilitiesService: TypeMoq.Mock<TestCapabilitiesService>;
let msSQLCapabilities: ProviderFeatures;
let capabilities: ProviderFeatures[];
let onCapabilitiesRegistered = new Emitter<ProviderFeatures>();
+1 -6
View File
@@ -2,15 +2,10 @@
"extends": "./tsconfig.json",
"compilerOptions": {
"noEmit": true,
"noImplicitAny": true,
"experimentalDecorators": true,
"noImplicitReturns": true,
"noUnusedLocals": true,
"noImplicitThis": true,
"alwaysStrict": true,
"strictBindCallApply": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"skipLibCheck": true
},
+3 -5
View File
@@ -25,7 +25,7 @@ export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
/**
* Create html nodes for the given content element.
*/
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}): HTMLElement {
export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRenderOptions = {}, markedOptions: marked.MarkedOptions = {}): HTMLElement {
const element = createElement(options);
const _uriMassage = function (part: string): string {
@@ -183,10 +183,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
}));
}
const markedOptions: marked.MarkedOptions = {
sanitize: true,
renderer
};
markedOptions.sanitize = true;
markedOptions.renderer = renderer;
const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource];
if (markdown.isTrusted) {
+1
View File
@@ -197,6 +197,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
get contentHeight(): number { return this.rangeMap.size; }
get onDidScroll(): Event<ScrollEvent> { return this.scrollableElement.onScroll; }
get onWillScroll(): Event<ScrollEvent> { return this.scrollableElement.onWillScroll; }
constructor(
container: HTMLElement,
@@ -167,6 +167,9 @@ export abstract class AbstractScrollableElement extends Widget {
private readonly _onScroll = this._register(new Emitter<ScrollEvent>());
public readonly onScroll: Event<ScrollEvent> = this._onScroll.event;
private readonly _onWillScroll = this._register(new Emitter<ScrollEvent>());
public readonly onWillScroll: Event<ScrollEvent> = this._onWillScroll.event;
protected constructor(element: HTMLElement, options: ScrollableElementCreationOptions, scrollable: Scrollable) {
super();
element.style.overflow = 'hidden';
@@ -174,6 +177,7 @@ export abstract class AbstractScrollableElement extends Widget {
this._scrollable = scrollable;
this._register(this._scrollable.onScroll((e) => {
this._onWillScroll.fire(e);
this._onDidScroll(e);
this._onScroll.fire(e);
}));
+14
View File
@@ -56,6 +56,20 @@ export function raceCancellation<T>(promise: Promise<T>, token: CancellationToke
return Promise.race([promise, new Promise<T>(resolve => token.onCancellationRequested(() => resolve(defaultValue)))]);
}
export function raceTimeout<T>(promise: Promise<T>, timeout: number, onTimeout?: () => void): Promise<T> {
let promiseResolve: (() => void) | undefined = undefined;
const timer = setTimeout(() => {
promiseResolve?.();
onTimeout?.();
}, timeout);
return Promise.race([
promise.finally(() => clearTimeout(timer)),
new Promise<T>(resolve => promiseResolve = resolve)
]);
}
export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
return new Promise<T>((resolve, reject) => {
const item = callback();
+2 -2
View File
@@ -13,7 +13,7 @@ const month = day * 30;
const year = day * 365;
// TODO[ECA]: Localize strings
export function fromNow(date: number | Date) {
export function fromNow(date: number | Date, appendAgoLabel?: boolean): string {
if (typeof date !== 'number') {
date = date.getTime();
}
@@ -48,7 +48,7 @@ export function fromNow(date: number | Date) {
unit = 'yr';
}
return `${value} ${unit}${value === 1 ? '' : 's'}`;
return `${value} ${unit}${value === 1 ? '' : 's'}${appendAgoLabel ? ' ago' : ''}`;
}
@@ -0,0 +1,24 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IDragAndDropData } from 'vs/base/browser/dnd';
export class CompositeDragAndDropData implements IDragAndDropData {
constructor(private type: 'view' | 'composite', private id: string) { }
update(dataTransfer: DataTransfer): void {
// no-op
}
getData(): {
type: 'view' | 'composite';
id: string;
} {
return { type: this.type, id: this.id };
}
}
export interface ICompositeDragAndDrop {
drop(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): void;
onDragOver(data: IDragAndDropData, target: string | undefined, originalEvent: DragEvent): boolean;
}
+42
View File
@@ -7,6 +7,7 @@ import * as assert from 'assert';
import * as async from 'vs/base/common/async';
import { isPromiseCanceledError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
suite('Async', () => {
@@ -646,4 +647,45 @@ suite('Async', () => {
assert.ok(pendingCancelled);
});
test('raceCancellation', async () => {
const cts = new CancellationTokenSource();
const now = Date.now();
const p = async.raceCancellation(async.timeout(100), cts.token);
cts.cancel();
await p;
assert.ok(Date.now() - now < 100);
});
test('raceTimeout', async () => {
const cts = new CancellationTokenSource();
// timeout wins
let now = Date.now();
let timedout = false;
const p1 = async.raceTimeout(async.timeout(100), 1, () => timedout = true);
cts.cancel();
await p1;
assert.ok(Date.now() - now < 100);
assert.equal(timedout, true);
// promise wins
now = Date.now();
timedout = false;
const p2 = async.raceTimeout(async.timeout(1), 100, () => timedout = true);
cts.cancel();
await p2;
assert.ok(Date.now() - now < 100);
assert.equal(timedout, false);
});
});
@@ -97,6 +97,23 @@ export class IssueReporter extends Disposable {
this.previewButton = new Button(issueReporterElement);
}
const issueTitle = configuration.data.issueTitle;
if (issueTitle) {
const issueTitleElement = this.getElementById<HTMLInputElement>('issue-title');
if (issueTitleElement) {
issueTitleElement.value = issueTitle;
}
}
const issueBody = configuration.data.issueBody;
if (issueBody) {
const description = this.getElementById<HTMLTextAreaElement>('description');
if (description) {
description.value = issueBody;
this.issueReporterModel.update({ issueDescription: issueBody });
}
}
ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial<IssueReporterData>) => {
this.logService.trace('issueReporter: Received performance data');
this.issueReporterModel.update(info);
@@ -1176,8 +1193,8 @@ export class IssueReporter extends Disposable {
}
}
private getElementById(elementId: string): HTMLElement | undefined {
const element = document.getElementById(elementId);
private getElementById<T extends HTMLElement = HTMLElement>(elementId: string): T | undefined {
const element = document.getElementById(elementId) as T | undefined;
if (element) {
return element;
} else {
@@ -49,10 +49,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, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAuthTokenServiceChannel, UserDataAutoSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, SettingsSyncChannel, UserDataAutoSyncChannel } 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';
@@ -60,12 +60,13 @@ import { ICredentialsService } from 'vs/platform/credentials/common/credentials'
import { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
import { UserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataAuthTokenService';
import { NativeStorageService } from 'vs/platform/storage/node/storageService';
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { IStorageService } from 'vs/platform/storage/common/storage';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService';
import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc';
export interface ISharedProcessConfiguration {
readonly machineId: string;
@@ -188,7 +189,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService));
services.set(IUserDataAuthTokenService, new SyncDescriptor(UserDataAuthTokenService));
services.set(IAuthenticationTokenService, new SyncDescriptor(AuthenticationTokenService));
services.set(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
@@ -214,8 +215,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService);
server.registerChannel('diagnostics', diagnosticsChannel);
const authTokenService = accessor.get(IUserDataAuthTokenService);
const authTokenChannel = new UserDataAuthTokenServiceChannel(authTokenService);
const authTokenService = accessor.get(IAuthenticationTokenService);
const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
server.registerChannel('authToken', authTokenChannel);
const settingsSyncService = accessor.get(ISettingsSyncService);
+142 -125
View File
@@ -25,6 +25,7 @@ import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
const CORE_WEIGHT = KeybindingWeight.EditorCore;
@@ -1529,6 +1530,102 @@ export namespace CoreNavigationCommands {
});
}
/**
* A command that will:
* 1. invoke a command on the focused editor.
* 2. otherwise, invoke a browser built-in command on the `activeElement`.
* 3. otherwise, invoke a command on the workbench active editor.
*/
abstract class EditorOrNativeTextInputCommand extends Command {
public runCommand(accessor: ServicesAccessor, args: any): void {
const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
// Only if editor text focus (i.e. not if editor has widget focus).
if (focusedEditor && focusedEditor.hasTextFocus()) {
return this.runEditorCommand(accessor, focusedEditor, args);
}
// Ignore this action when user is focused on an element that allows for entering text
const activeElement = <HTMLElement>document.activeElement;
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
return this.runDOMCommand();
}
// Redirecting to active editor
const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor();
if (activeEditor) {
activeEditor.focus();
return this.runEditorCommand(accessor, activeEditor, args);
}
}
public abstract runDOMCommand(): void;
public abstract runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void;
}
class SelectAllCommand extends EditorOrNativeTextInputCommand {
constructor() {
super({
id: 'editor.action.selectAll',
precondition: EditorContextKeys.textInputFocus,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: null,
primary: KeyMod.CtrlCmd | KeyCode.KEY_A
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu, // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu
group: '4_find_global', // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu
title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('selectAll', "Select All"),
order: 1
}]
});
}
public runDOMCommand(): void {
document.execCommand('selectAll');
}
public runEditorCommand(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
args = args || {};
args.source = 'keyboard';
CoreNavigationCommands.SelectAll.runEditorCommand(accessor, editor, args);
}
}
class UndoCommand extends EditorOrNativeTextInputCommand {
public runDOMCommand(): void {
document.execCommand('undo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().undo();
}
}
class RedoCommand extends EditorOrNativeTextInputCommand {
public runDOMCommand(): void {
document.execCommand('redo');
}
public runEditorCommand(accessor: ServicesAccessor | null, editor: ICodeEditor, args: any): void {
if (!editor.hasModel() || editor.getOption(EditorOption.readOnly) === true) {
return;
}
editor.getModel().redo();
}
}
function registerCommand<T extends Command>(command: T): T {
command.register();
return command;
}
export namespace CoreEditingCommands {
export abstract class CoreEditingCommand extends EditorCommand {
@@ -1659,62 +1756,53 @@ export namespace CoreEditingCommands {
}
});
}
export const Undo: UndoCommand = registerCommand(new UndoCommand({
id: 'undo',
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('undo', "Undo"),
order: 1
}]
}));
function registerCommand(command: Command) {
command.register();
}
export const DefaultUndo: UndoCommand = registerCommand(new UndoCommand({ id: 'default:undo', precondition: EditorContextKeys.writable }));
/**
* A command that will:
* 1. invoke a command on the focused editor.
* 2. otherwise, invoke a browser built-in command on the `activeElement`.
* 3. otherwise, invoke a command on the workbench active editor.
*/
class EditorOrNativeTextInputCommand extends Command {
export const Redo: RedoCommand = registerCommand(new RedoCommand({
id: 'redo',
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Y,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z],
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
order: 2
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('redo', "Redo"),
order: 1
}]
}));
private readonly _editorHandler: string | EditorCommand;
private readonly _inputHandler: string;
constructor(opts: ICommandOptions & { editorHandler: string | EditorCommand; inputHandler: string; }) {
super(opts);
this._editorHandler = opts.editorHandler;
this._inputHandler = opts.inputHandler;
}
public runCommand(accessor: ServicesAccessor, args: any): void {
const focusedEditor = accessor.get(ICodeEditorService).getFocusedCodeEditor();
// Only if editor text focus (i.e. not if editor has widget focus).
if (focusedEditor && focusedEditor.hasTextFocus()) {
return this._runEditorHandler(accessor, focusedEditor, args);
}
// Ignore this action when user is focused on an element that allows for entering text
const activeElement = <HTMLElement>document.activeElement;
if (activeElement && ['input', 'textarea'].indexOf(activeElement.tagName.toLowerCase()) >= 0) {
document.execCommand(this._inputHandler);
return;
}
// Redirecting to active editor
const activeEditor = accessor.get(ICodeEditorService).getActiveCodeEditor();
if (activeEditor) {
activeEditor.focus();
return this._runEditorHandler(accessor, activeEditor, args);
}
}
private _runEditorHandler(accessor: ServicesAccessor, editor: ICodeEditor, args: any): void {
const HANDLER = this._editorHandler;
if (typeof HANDLER === 'string') {
editor.trigger('keyboard', HANDLER, args);
} else {
args = args || {};
args.source = 'keyboard';
HANDLER.runEditorCommand(accessor, editor, args);
}
}
export const DefaultRedo: RedoCommand = registerCommand(new RedoCommand({ id: 'default:redo', precondition: EditorContextKeys.writable }));
}
/**
@@ -1743,78 +1831,7 @@ class EditorHandlerCommand extends Command {
}
}
registerCommand(new EditorOrNativeTextInputCommand({
editorHandler: CoreNavigationCommands.SelectAll,
inputHandler: 'selectAll',
id: 'editor.action.selectAll',
precondition: EditorContextKeys.textInputFocus,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: null,
primary: KeyMod.CtrlCmd | KeyCode.KEY_A
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu, // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu
group: '4_find_global', // {{SQL CARBON EDIT}} - Put this in the edit menu since we disabled the selection menu
title: nls.localize({ key: 'miSelectAll', comment: ['&& denotes a mnemonic'] }, "&&Select All"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('selectAll', "Select All"),
order: 1
}]
}));
registerCommand(new EditorOrNativeTextInputCommand({
editorHandler: Handler.Undo,
inputHandler: 'undo',
id: Handler.Undo,
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Z
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miUndo', comment: ['&& denotes a mnemonic'] }, "&&Undo"),
order: 1
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('undo', "Undo"),
order: 1
}]
}));
registerCommand(new EditorHandlerCommand('default:' + Handler.Undo, Handler.Undo));
registerCommand(new EditorOrNativeTextInputCommand({
editorHandler: Handler.Redo,
inputHandler: 'redo',
id: Handler.Redo,
precondition: EditorContextKeys.writable,
kbOpts: {
weight: CORE_WEIGHT,
kbExpr: EditorContextKeys.textInputFocus,
primary: KeyMod.CtrlCmd | KeyCode.KEY_Y,
secondary: [KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z],
mac: { primary: KeyMod.CtrlCmd | KeyMod.Shift | KeyCode.KEY_Z }
},
menuOpts: [{
menuId: MenuId.MenubarEditMenu,
group: '1_do',
title: nls.localize({ key: 'miRedo', comment: ['&& denotes a mnemonic'] }, "&&Redo"),
order: 2
}, {
menuId: MenuId.CommandPalette,
group: '',
title: nls.localize('redo', "Redo"),
order: 1
}]
}));
registerCommand(new EditorHandlerCommand('default:' + Handler.Redo, Handler.Redo));
registerCommand(new SelectAllCommand());
function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void {
registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId));
@@ -589,14 +589,19 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
if (boxEndY - boxStartY > viewportHeight) {
// the box is larger than the viewport ... scroll to its top
newScrollTop = boxStartY;
} else if (verticalType === viewEvents.VerticalRevealType.NearTop) {
// We want a gap that is 20% of the viewport, but with a minimum of 5 lines
const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);
// Try to scroll just above the box with the desired gap
const desiredScrollTop = boxStartY - desiredGapAbove;
// But ensure that the box is not pushed out of viewport
const minScrollTop = boxEndY - viewportHeight;
newScrollTop = Math.max(minScrollTop, desiredScrollTop);
} else if (verticalType === viewEvents.VerticalRevealType.NearTop || verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport) {
if (verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
// Box is already in the viewport... do nothing
newScrollTop = viewportStartY;
} else {
// We want a gap that is 20% of the viewport, but with a minimum of 5 lines
const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);
// Try to scroll just above the box with the desired gap
const desiredScrollTop = boxStartY - desiredGapAbove;
// But ensure that the box is not pushed out of viewport
const minScrollTop = boxEndY - viewportHeight;
newScrollTop = Math.max(minScrollTop, desiredScrollTop);
}
} else if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) {
if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
// Box is already in the viewport... do nothing
@@ -13,7 +13,7 @@ import * as platform from 'vs/base/common/platform';
import * as strings from 'vs/base/common/strings';
import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer';
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart';
import { RenderMinimap, EditorOption, MINIMAP_GUTTER_WIDTH, EditorLayoutInfoComputer, IComputedEditorOptions } from 'vs/editor/common/config/editorOptions';
import { RenderMinimap, EditorOption, MINIMAP_GUTTER_WIDTH, EditorLayoutInfoComputer } from 'vs/editor/common/config/editorOptions';
import { Range } from 'vs/editor/common/core/range';
import { RGBA8 } from 'vs/editor/common/core/rgba';
import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon';
@@ -91,6 +91,8 @@ class MinimapOptions {
*/
public readonly canvasOuterHeight: number;
public readonly isSampling: boolean;
public readonly editorHeight: number;
public readonly fontScale: number;
public readonly minimapLineHeight: number;
public readonly minimapCharWidth: number;
@@ -122,6 +124,8 @@ class MinimapOptions {
this.canvasOuterWidth = layoutInfo.minimapCanvasOuterWidth;
this.canvasOuterHeight = layoutInfo.minimapCanvasOuterHeight;
this.isSampling = layoutInfo.minimapIsSampling;
this.editorHeight = layoutInfo.height;
this.fontScale = layoutInfo.minimapScale;
this.minimapLineHeight = layoutInfo.minimapLineHeight;
this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale;
@@ -154,6 +158,8 @@ class MinimapOptions {
&& this.canvasInnerHeight === other.canvasInnerHeight
&& this.canvasOuterWidth === other.canvasOuterWidth
&& this.canvasOuterHeight === other.canvasOuterHeight
&& this.isSampling === other.isSampling
&& this.editorHeight === other.editorHeight
&& this.fontScale === other.fontScale
&& this.minimapLineHeight === other.minimapLineHeight
&& this.minimapCharWidth === other.minimapCharWidth
@@ -527,26 +533,24 @@ type SamplingStateEvent = SamplingStateLinesInsertedEvent | SamplingStateLinesDe
class MinimapSamplingState {
public static compute(options: IComputedEditorOptions, modelLineCount: number, oldSamplingState: MinimapSamplingState | null): [MinimapSamplingState | null, SamplingStateEvent[]] {
const minimapOpts = options.get(EditorOption.minimap);
const layoutInfo = options.get(EditorOption.layoutInfo);
if (!minimapOpts.enabled || !layoutInfo.minimapIsSampling) {
public static compute(options: MinimapOptions, viewLineCount: number, oldSamplingState: MinimapSamplingState | null): [MinimapSamplingState | null, SamplingStateEvent[]] {
if (options.renderMinimap === RenderMinimap.None || !options.isSampling) {
return [null, []];
}
// ratio is intentionally not part of the layout to avoid the layout changing all the time
// so we need to recompute it again...
const pixelRatio = options.get(EditorOption.pixelRatio);
const lineHeight = options.get(EditorOption.lineHeight);
const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine);
const pixelRatio = options.pixelRatio;
const lineHeight = options.lineHeight;
const scrollBeyondLastLine = options.scrollBeyondLastLine;
const { minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
modelLineCount: modelLineCount,
viewLineCount: viewLineCount,
scrollBeyondLastLine: scrollBeyondLastLine,
height: layoutInfo.height,
height: options.editorHeight,
lineHeight: lineHeight,
pixelRatio: pixelRatio
});
const ratio = modelLineCount / minimapLineCount;
const ratio = viewLineCount / minimapLineCount;
const halfRatio = ratio / 2;
if (!oldSamplingState || oldSamplingState.minimapLines.length === 0) {
@@ -556,7 +560,7 @@ class MinimapSamplingState {
for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) {
result[i] = Math.round(i * ratio + halfRatio);
}
result[minimapLineCount - 1] = modelLineCount;
result[minimapLineCount - 1] = viewLineCount;
}
return [new MinimapSamplingState(ratio, result), []];
}
@@ -566,15 +570,15 @@ class MinimapSamplingState {
let result: number[] = [];
let oldIndex = 0;
let oldDeltaLineCount = 0;
let minModelLineNumber = 1;
let minViewLineNumber = 1;
const MAX_EVENT_COUNT = 10; // generate at most 10 events, if there are more than 10 changes, just flush all previous data
let events: SamplingStateEvent[] = [];
let lastEvent: SamplingStateEvent | null = null;
for (let i = 0; i < minimapLineCount; i++) {
const fromModelLineNumber = Math.max(minModelLineNumber, Math.round(i * ratio));
const toModelLineNumber = Math.max(fromModelLineNumber, Math.round((i + 1) * ratio));
const fromViewLineNumber = Math.max(minViewLineNumber, Math.round(i * ratio));
const toViewLineNumber = Math.max(fromViewLineNumber, Math.round((i + 1) * ratio));
while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromModelLineNumber) {
while (oldIndex < oldLength && oldMinimapLines[oldIndex] < fromViewLineNumber) {
if (events.length < MAX_EVENT_COUNT) {
const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) {
@@ -588,18 +592,18 @@ class MinimapSamplingState {
oldIndex++;
}
let selectedModelLineNumber: number;
if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toModelLineNumber) {
let selectedViewLineNumber: number;
if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toViewLineNumber) {
// reuse the old sampled line
selectedModelLineNumber = oldMinimapLines[oldIndex];
selectedViewLineNumber = oldMinimapLines[oldIndex];
oldIndex++;
} else {
if (i === 0) {
selectedModelLineNumber = 1;
selectedViewLineNumber = 1;
} else if (i + 1 === minimapLineCount) {
selectedModelLineNumber = modelLineCount;
selectedViewLineNumber = viewLineCount;
} else {
selectedModelLineNumber = Math.round(i * ratio + halfRatio);
selectedViewLineNumber = Math.round(i * ratio + halfRatio);
}
if (events.length < MAX_EVENT_COUNT) {
const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
@@ -613,8 +617,8 @@ class MinimapSamplingState {
}
}
result[i] = selectedModelLineNumber;
minModelLineNumber = selectedModelLineNumber;
result[i] = selectedViewLineNumber;
minViewLineNumber = selectedViewLineNumber;
}
if (events.length < MAX_EVENT_COUNT) {
@@ -743,7 +747,7 @@ export class Minimap extends ViewPart implements IMinimapModel {
this._minimapSelections = null;
this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker);
const [samplingState,] = MinimapSamplingState.compute(this._context.configuration.options, this._context.model.getLineCount(), null);
const [samplingState,] = MinimapSamplingState.compute(this.options, this._context.model.getLineCount(), null);
this._samplingState = samplingState;
this._shouldCheckSampling = false;
@@ -787,6 +791,9 @@ export class Minimap extends ViewPart implements IMinimapModel {
return false;
}
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
if (this._samplingState) {
this._shouldCheckSampling = true;
}
return this._actual.onFlushed();
}
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
@@ -898,7 +905,7 @@ export class Minimap extends ViewPart implements IMinimapModel {
this._minimapSelections = null;
const wasSampling = Boolean(this._samplingState);
const [samplingState, events] = MinimapSamplingState.compute(this._context.configuration.options, this._context.model.getLineCount(), this._samplingState);
const [samplingState, events] = MinimapSamplingState.compute(this.options, this._context.model.getLineCount(), this._samplingState);
this._samplingState = samplingState;
if (wasSampling && this._samplingState) {
@@ -756,6 +756,15 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
);
}
public revealRangeNearTopIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
this._revealRange(
range,
VerticalRevealType.NearTopIfOutsideViewport,
true,
scrollType
);
}
public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
this._revealRange(
range,
@@ -1534,11 +1543,18 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
};
}
const onDidChangeTextFocus = (textFocus: boolean) => {
if (this._modelData) {
this._modelData.cursor.setHasFocus(textFocus);
}
this._editorTextFocus.setValue(textFocus);
};
const viewOutgoingEvents = new ViewOutgoingEvents(viewModel);
viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e);
viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e);
viewOutgoingEvents.onDidGainFocus = () => this._editorTextFocus.setValue(true);
viewOutgoingEvents.onDidLoseFocus = () => this._editorTextFocus.setValue(false);
viewOutgoingEvents.onDidGainFocus = () => onDidChangeTextFocus(true);
viewOutgoingEvents.onDidLoseFocus = () => onDidChangeTextFocus(false);
viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e);
viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e);
viewOutgoingEvents.onMouseUp = (e) => this._onMouseUp.fire(e);
@@ -1579,7 +1595,7 @@ export class CodeEditorWidget extends Disposable implements editorBrowser.ICodeE
this._modelData = null;
this._domElement.removeAttribute('data-mode-id');
if (removeDomNode) {
if (removeDomNode && this._domElement.contains(removeDomNode)) {
this._domElement.removeChild(removeDomNode);
}
@@ -830,6 +830,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this.modifiedEditor.revealRangeNearTop(range, scrollType);
}
public revealRangeNearTopIfOutsideViewport(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
this.modifiedEditor.revealRangeNearTopIfOutsideViewport(range, scrollType);
}
public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
this.modifiedEditor.revealRangeAtTop(range, scrollType);
}
@@ -287,7 +287,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
public options!: ComputedEditorOptions;
private _isDominatedByLongLines: boolean;
private _maxLineNumber: number;
private _viewLineCount: number;
private _lineNumbersDigitCount: number;
private _rawOptions: IEditorOptions;
@@ -299,7 +299,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
this.isSimpleWidget = isSimpleWidget;
this._isDominatedByLongLines = false;
this._maxLineNumber = 1;
this._viewLineCount = 1;
this._lineNumbersDigitCount = 1;
this._rawOptions = deepCloneAndMigrateOptions(_options);
@@ -349,7 +349,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
fontInfo: this.readConfiguration(bareFontInfo),
extraEditorClassName: partialEnv.extraEditorClassName,
isDominatedByLongLines: this._isDominatedByLongLines,
maxLineNumber: this._maxLineNumber,
viewLineCount: this._viewLineCount,
lineNumbersDigitCount: this._lineNumbersDigitCount,
emptySelectionClipboard: partialEnv.emptySelectionClipboard,
pixelRatio: partialEnv.pixelRatio,
@@ -408,11 +408,19 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
}
public setMaxLineNumber(maxLineNumber: number): void {
if (this._maxLineNumber === maxLineNumber) {
const lineNumbersDigitCount = CommonEditorConfiguration._digitCount(maxLineNumber);
if (this._lineNumbersDigitCount === lineNumbersDigitCount) {
return;
}
this._maxLineNumber = maxLineNumber;
this._lineNumbersDigitCount = CommonEditorConfiguration._digitCount(maxLineNumber);
this._lineNumbersDigitCount = lineNumbersDigitCount;
this._recomputeOptions();
}
public setViewLineCount(viewLineCount: number): void {
if (this._viewLineCount === viewLineCount) {
return;
}
this._viewLineCount = viewLineCount;
this._recomputeOptions();
}
+11 -11
View File
@@ -678,7 +678,7 @@ export interface IEnvironmentalOptions {
readonly fontInfo: FontInfo;
readonly extraEditorClassName: string;
readonly isDominatedByLongLines: boolean;
readonly maxLineNumber: number;
readonly viewLineCount: number;
readonly lineNumbersDigitCount: number;
readonly emptySelectionClipboard: boolean;
readonly pixelRatio: number;
@@ -1733,7 +1733,7 @@ export interface EditorLayoutInfoComputerEnv {
outerWidth: number;
outerHeight: number;
lineHeight: number;
maxLineNumber: number;
viewLineCount: number;
lineNumbersDigitCount: number;
typicalHalfwidthCharacterWidth: number;
maxDigitWidth: number;
@@ -1757,7 +1757,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
outerWidth: env.outerWidth,
outerHeight: env.outerHeight,
lineHeight: env.fontInfo.lineHeight,
maxLineNumber: env.maxLineNumber,
viewLineCount: env.viewLineCount,
lineNumbersDigitCount: env.lineNumbersDigitCount,
typicalHalfwidthCharacterWidth: env.fontInfo.typicalHalfwidthCharacterWidth,
maxDigitWidth: env.fontInfo.maxDigitWidth,
@@ -1766,7 +1766,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
}
public static computeContainedMinimapLineCount(input: {
modelLineCount: number;
viewLineCount: number;
scrollBeyondLastLine: boolean;
height: number;
lineHeight: number;
@@ -1774,8 +1774,8 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
}): { typicalViewportLineCount: number; extraLinesBeyondLastLine: number; desiredRatio: number; minimapLineCount: number; } {
const typicalViewportLineCount = input.height / input.lineHeight;
const extraLinesBeyondLastLine = input.scrollBeyondLastLine ? (typicalViewportLineCount - 1) : 0;
const desiredRatio = (input.modelLineCount + extraLinesBeyondLastLine) / (input.pixelRatio * input.height);
const minimapLineCount = Math.floor(input.modelLineCount / desiredRatio);
const desiredRatio = (input.viewLineCount + extraLinesBeyondLastLine) / (input.pixelRatio * input.height);
const minimapLineCount = Math.floor(input.viewLineCount / desiredRatio);
return { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount };
}
@@ -1863,9 +1863,9 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
let minimapWidthMultiplier: number = 1;
if (minimapMode === 'cover' || minimapMode === 'contain') {
const modelLineCount = env.maxLineNumber;
const viewLineCount = env.viewLineCount;
const { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
modelLineCount: modelLineCount,
viewLineCount: viewLineCount,
scrollBeyondLastLine: scrollBeyondLastLine,
height: outerHeight,
lineHeight: lineHeight,
@@ -1873,7 +1873,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
});
// ratio is intentionally not part of the layout to avoid the layout changing all the time
// when doing sampling
const ratio = modelLineCount / minimapLineCount;
const ratio = viewLineCount / minimapLineCount;
if (ratio > 1) {
minimapHeightIsEditorHeight = true;
@@ -1882,7 +1882,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
minimapLineHeight = 1;
minimapCharWidth = minimapScale / pixelRatio;
} else {
const effectiveMinimapHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
const effectiveMinimapHeight = Math.ceil((viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
if (minimapMode === 'cover' || effectiveMinimapHeight > minimapCanvasInnerHeight) {
minimapHeightIsEditorHeight = true;
const configuredFontScale = minimapScale;
@@ -1892,7 +1892,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale);
}
minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier;
minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, modelLineCount + extraLinesBeyondLastLine)) * minimapLineHeight);
minimapCanvasInnerHeight = Math.ceil((Math.max(typicalViewportLineCount, viewLineCount + extraLinesBeyondLastLine)) * minimapLineHeight);
}
}
}
+19 -24
View File
@@ -16,7 +16,7 @@ import { Range, IRange } from 'vs/editor/common/core/range';
import { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon';
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { RawContentChangedType } from 'vs/editor/common/model/textModelEvents';
import { RawContentChangedType, ModelRawContentChangedEvent } from 'vs/editor/common/model/textModelEvents';
import * as viewEvents from 'vs/editor/common/view/viewEvents';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { dispose } from 'vs/base/common/lifecycle';
@@ -186,6 +186,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
public context: CursorContext;
private _cursors: CursorCollection;
private _hasFocus: boolean;
private _isHandling: boolean;
private _isDoingComposition: boolean;
private _selectionsWhenCompositionStarted: Selection[] | null;
@@ -202,6 +203,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this.context = new CursorContext(this._configuration, this._model, this._viewModel);
this._cursors = new CursorCollection(this.context);
this._hasFocus = false;
this._isHandling = false;
this._isDoingComposition = false;
this._selectionsWhenCompositionStarted = null;
@@ -215,8 +217,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
return;
}
let hadFlushEvent = e.containsEvent(RawContentChangedType.Flush);
this._onModelContentChanged(hadFlushEvent);
this._onModelContentChanged(e);
}));
this._register(viewModel.addEventListener((events: viewEvents.ViewEvent[]) => {
@@ -264,6 +265,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
super.dispose();
}
public setHasFocus(hasFocus: boolean): void {
this._hasFocus = hasFocus;
}
private _validateAutoClosedActions(): void {
if (this._autoClosedActions.length > 0) {
let selections: Range[] = this._cursors.getSelections();
@@ -392,8 +397,9 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this.reveal('restoreState', true, RevealTarget.Primary, editorCommon.ScrollType.Immediate);
}
private _onModelContentChanged(hadFlushEvent: boolean): void {
private _onModelContentChanged(e: ModelRawContentChangedEvent): void {
const hadFlushEvent = e.containsEvent(RawContentChangedType.Flush);
this._prevEditOperationType = EditOperationType.Other;
if (hadFlushEvent) {
@@ -403,8 +409,13 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._validateAutoClosedActions();
this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null);
} else {
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers));
if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) {
const cursorState = CursorState.fromModelSelections(e.resultingSelection);
this.setStates('modelChange', e.isUndoing ? CursorChangeReason.Undo : e.isRedoing ? CursorChangeReason.Redo : CursorChangeReason.RecoverFromMarkers, cursorState);
} else {
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers();
this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers));
}
}
}
@@ -704,11 +715,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
const oldState = new CursorModelState(this._model, this);
let cursorChangeReason = CursorChangeReason.NotSet;
if (handlerId !== H.Undo && handlerId !== H.Redo) {
// TODO@Alex: if the undo/redo stack contains non-null selections
// it would also be OK to stop tracking selections here
this._cursors.stopTrackingSelections();
}
this._cursors.stopTrackingSelections();
// ensure valid state on all cursors
this._cursors.ensureValidState();
@@ -734,16 +741,6 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._cut();
break;
case H.Undo:
cursorChangeReason = CursorChangeReason.Undo;
this._interpretCommandResult(this._model.undo());
break;
case H.Redo:
cursorChangeReason = CursorChangeReason.Redo;
this._interpretCommandResult(this._model.redo());
break;
case H.ExecuteCommand:
this._externalExecuteCommand(<editorCommon.ICommand>payload);
break;
@@ -762,9 +759,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._isHandling = false;
if (handlerId !== H.Undo && handlerId !== H.Redo) {
this._cursors.startTrackingSelections();
}
this._cursors.startTrackingSelections();
this._validateAutoClosedActions();
+7 -4
View File
@@ -154,6 +154,7 @@ export interface IConfiguration extends IDisposable {
readonly options: IComputedEditorOptions;
setMaxLineNumber(maxLineNumber: number): void;
setViewLineCount(viewLineCount: number): void;
updateOptions(newOptions: IEditorOptions): void;
getRawOptions(): IEditorOptions;
observeReferenceElement(dimension?: IDimension): void;
@@ -466,6 +467,12 @@ export interface IEditor {
*/
revealRangeNearTop(range: IRange, scrollType?: ScrollType): void;
/**
* Scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport,
* optimized for viewing a code definition. Only if it lies outside the viewport.
*/
revealRangeNearTopIfOutsideViewport(range: IRange, scrollType?: ScrollType): void;
/**
* Directly trigger a handler or an editor action.
* @param source The source of the call.
@@ -671,9 +678,5 @@ export const Handler = {
CompositionStart: 'compositionStart',
CompositionEnd: 'compositionEnd',
Paste: 'paste',
Cut: 'cut',
Undo: 'undo',
Redo: 'redo',
};
+19 -2
View File
@@ -379,6 +379,13 @@ export interface IValidEditOperation {
forceMoveMarkers: boolean;
}
/**
* @internal
*/
export interface IValidEditOperations {
operations: IValidEditOperation[];
}
/**
* A callback that can compute the cursor state after applying a series of edit operations.
*/
@@ -1086,18 +1093,28 @@ export interface ITextModel {
*/
applyEdits(operations: IIdentifiedSingleEditOperation[]): IValidEditOperation[];
/**
* @internal
*/
_applyEdits(edits: IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): IValidEditOperations[];
/**
* Change the end of line sequence without recording in the undo stack.
* This can have dire consequences on the undo stack! See @pushEOL for the preferred way.
*/
setEOL(eol: EndOfLineSequence): void;
/**
* @internal
*/
_setEOL(eol: EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void;
/**
* Undo edit operations until the first previous stop point created by `pushStackElement`.
* The inverse edit operations will be pushed on the redo stack.
* @internal
*/
undo(): Selection[] | null;
undo(): void;
/**
* Is there anything in the undo stack?
@@ -1110,7 +1127,7 @@ export interface ITextModel {
* The inverse edit operations will be pushed on the undo stack.
* @internal
*/
redo(): Selection[] | null;
redo(): void;
/**
* Is there anything in the redo stack?
+108 -171
View File
@@ -3,61 +3,72 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as nls from 'vs/nls';
import { onUnexpectedError } from 'vs/base/common/errors';
import { Selection } from 'vs/editor/common/core/selection';
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model';
import { EndOfLineSequence, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation, ITextModel, IValidEditOperations } from 'vs/editor/common/model';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IUndoRedoService, IUndoRedoElement, IUndoRedoContext } from 'vs/platform/undoRedo/common/undoRedo';
import { URI } from 'vs/base/common/uri';
interface IEditOperation {
operations: IValidEditOperation[];
}
class EditStackElement implements IUndoRedoElement {
interface IStackElement {
readonly beforeVersionId: number;
readonly beforeCursorState: Selection[] | null;
readonly afterCursorState: Selection[] | null;
readonly afterVersionId: number;
public readonly label: string;
private _isOpen: boolean;
private readonly _model: TextModel;
private readonly _beforeVersionId: number;
private readonly _beforeCursorState: Selection[];
private _afterVersionId: number;
private _afterCursorState: Selection[] | null;
private _edits: IValidEditOperations[];
undo(model: TextModel): void;
redo(model: TextModel): void;
}
class EditStackElement implements IStackElement {
public readonly beforeVersionId: number;
public readonly beforeCursorState: Selection[];
public afterCursorState: Selection[] | null;
public afterVersionId: number;
public editOperations: IEditOperation[];
constructor(beforeVersionId: number, beforeCursorState: Selection[]) {
this.beforeVersionId = beforeVersionId;
this.beforeCursorState = beforeCursorState;
this.afterCursorState = null;
this.afterVersionId = -1;
this.editOperations = [];
public get resources(): readonly URI[] {
return [this._model.uri];
}
public undo(model: TextModel): void {
// Apply all operations in reverse order
for (let i = this.editOperations.length - 1; i >= 0; i--) {
this.editOperations[i] = {
operations: model.applyEdits(this.editOperations[i].operations)
};
}
constructor(model: TextModel, beforeVersionId: number, beforeCursorState: Selection[], afterVersionId: number, afterCursorState: Selection[] | null, operations: IValidEditOperation[]) {
this.label = nls.localize('edit', "Typing");
this._isOpen = true;
this._model = model;
this._beforeVersionId = beforeVersionId;
this._beforeCursorState = beforeCursorState;
this._afterVersionId = afterVersionId;
this._afterCursorState = afterCursorState;
this._edits = [{ operations: operations }];
}
public redo(model: TextModel): void {
// Apply all operations
for (let i = 0; i < this.editOperations.length; i++) {
this.editOperations[i] = {
operations: model.applyEdits(this.editOperations[i].operations)
};
}
public isOpen(): boolean {
return this._isOpen;
}
public append(operations: IValidEditOperation[], afterVersionId: number, afterCursorState: Selection[] | null): void {
this._edits.push({ operations: operations });
this._afterVersionId = afterVersionId;
this._afterCursorState = afterCursorState;
}
public close(): void {
this._isOpen = false;
}
undo(ctx: IUndoRedoContext): void {
this._isOpen = false;
this._edits.reverse();
this._edits = this._model._applyEdits(this._edits, true, false, this._beforeVersionId, this._beforeCursorState);
}
redo(ctx: IUndoRedoContext): void {
this._isOpen = false;
this._edits.reverse();
this._edits = this._model._applyEdits(this._edits, false, true, this._afterVersionId, this._afterCursorState);
}
invalidate(resource: URI): void {
// nothing to do
}
}
function getModelEOL(model: TextModel): EndOfLineSequence {
function getModelEOL(model: ITextModel): EndOfLineSequence {
const eol = model.getEOL();
if (eol === '\n') {
return EndOfLineSequence.LF;
@@ -66,32 +77,40 @@ function getModelEOL(model: TextModel): EndOfLineSequence {
}
}
class EOLStackElement implements IStackElement {
public readonly beforeVersionId: number;
public readonly beforeCursorState: Selection[] | null;
public readonly afterCursorState: Selection[] | null;
public afterVersionId: number;
class EOLStackElement implements IUndoRedoElement {
public eol: EndOfLineSequence;
public readonly label: string;
private readonly _model: TextModel;
private readonly _beforeVersionId: number;
private readonly _afterVersionId: number;
private _eol: EndOfLineSequence;
constructor(beforeVersionId: number, setEOL: EndOfLineSequence) {
this.beforeVersionId = beforeVersionId;
this.beforeCursorState = null;
this.afterCursorState = null;
this.afterVersionId = -1;
this.eol = setEOL;
public get resources(): readonly URI[] {
return [this._model.uri];
}
public undo(model: TextModel): void {
let redoEOL = getModelEOL(model);
model.setEOL(this.eol);
this.eol = redoEOL;
constructor(model: TextModel, beforeVersionId: number, afterVersionId: number, eol: EndOfLineSequence) {
this.label = nls.localize('eol', "Change End Of Line Sequence");
this._model = model;
this._beforeVersionId = beforeVersionId;
this._afterVersionId = afterVersionId;
this._eol = eol;
}
public redo(model: TextModel): void {
let undoEOL = getModelEOL(model);
model.setEOL(this.eol);
this.eol = undoEOL;
undo(ctx: IUndoRedoContext): void {
const redoEOL = getModelEOL(this._model);
this._model._setEOL(this._eol, true, false, this._beforeVersionId, null);
this._eol = redoEOL;
}
redo(ctx: IUndoRedoContext): void {
const undoEOL = getModelEOL(this._model);
this._model._setEOL(this._eol, false, true, this._afterVersionId, null);
this._eol = undoEOL;
}
invalidate(resource: URI): void {
// nothing to do
}
}
@@ -102,76 +121,52 @@ export interface IUndoRedoResult {
export class EditStack {
private readonly model: TextModel;
private currentOpenStackElement: IStackElement | null;
private past: IStackElement[];
private future: IStackElement[];
private readonly _model: TextModel;
private readonly _undoRedoService: IUndoRedoService;
constructor(model: TextModel) {
this.model = model;
this.currentOpenStackElement = null;
this.past = [];
this.future = [];
constructor(model: TextModel, undoRedoService: IUndoRedoService) {
this._model = model;
this._undoRedoService = undoRedoService;
}
public pushStackElement(): void {
if (this.currentOpenStackElement !== null) {
this.past.push(this.currentOpenStackElement);
this.currentOpenStackElement = null;
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (lastElement && lastElement instanceof EditStackElement) {
lastElement.close();
}
}
public clear(): void {
this.currentOpenStackElement = null;
this.past = [];
this.future = [];
this._undoRedoService.removeElements(this._model.uri);
}
public pushEOL(eol: EndOfLineSequence): void {
// No support for parallel universes :(
this.future = [];
const beforeVersionId = this._model.getAlternativeVersionId();
const inverseEOL = getModelEOL(this._model);
this._model.setEOL(eol);
const afterVersionId = this._model.getAlternativeVersionId();
if (this.currentOpenStackElement) {
this.pushStackElement();
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (lastElement && lastElement instanceof EditStackElement) {
lastElement.close();
}
const prevEOL = getModelEOL(this.model);
let stackElement = new EOLStackElement(this.model.getAlternativeVersionId(), prevEOL);
this.model.setEOL(eol);
stackElement.afterVersionId = this.model.getVersionId();
this.currentOpenStackElement = stackElement;
this.pushStackElement();
this._undoRedoService.pushElement(new EOLStackElement(this._model, inverseEOL, beforeVersionId, afterVersionId));
}
public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null {
// No support for parallel universes :(
this.future = [];
const beforeVersionId = this._model.getAlternativeVersionId();
const inverseEditOperations = this._model.applyEdits(editOperations);
const afterVersionId = this._model.getAlternativeVersionId();
const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
let stackElement: EditStackElement | null = null;
if (this.currentOpenStackElement) {
if (this.currentOpenStackElement instanceof EditStackElement) {
stackElement = this.currentOpenStackElement;
} else {
this.pushStackElement();
}
const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (lastElement && lastElement instanceof EditStackElement && lastElement.isOpen()) {
lastElement.append(inverseEditOperations, afterVersionId, afterCursorState);
} else {
this._undoRedoService.pushElement(new EditStackElement(this._model, beforeVersionId, beforeCursorState, afterVersionId, afterCursorState, inverseEditOperations));
}
if (!this.currentOpenStackElement) {
stackElement = new EditStackElement(this.model.getAlternativeVersionId(), beforeCursorState);
this.currentOpenStackElement = stackElement;
}
const inverseEditOperation: IEditOperation = {
operations: this.model.applyEdits(editOperations)
};
stackElement!.editOperations.push(inverseEditOperation);
stackElement!.afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperation.operations);
stackElement!.afterVersionId = this.model.getVersionId();
return stackElement!.afterCursorState;
return afterCursorState;
}
private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null {
@@ -182,62 +177,4 @@ export class EditStack {
return null;
}
}
public undo(): IUndoRedoResult | null {
this.pushStackElement();
if (this.past.length > 0) {
const pastStackElement = this.past.pop()!;
try {
pastStackElement.undo(this.model);
} catch (e) {
onUnexpectedError(e);
this.clear();
return null;
}
this.future.push(pastStackElement);
return {
selections: pastStackElement.beforeCursorState,
recordedVersionId: pastStackElement.beforeVersionId
};
}
return null;
}
public canUndo(): boolean {
return (this.past.length > 0) || this.currentOpenStackElement !== null;
}
public redo(): IUndoRedoResult | null {
if (this.future.length > 0) {
const futureStackElement = this.future.pop()!;
try {
futureStackElement.redo(this.model);
} catch (e) {
onUnexpectedError(e);
this.clear();
return null;
}
this.past.push(futureStackElement);
return {
selections: futureStackElement.afterCursorState,
recordedVersionId: futureStackElement.afterVersionId
};
}
return null;
}
public canRedo(): boolean {
return (this.future.length > 0);
}
}
+60 -56
View File
@@ -36,6 +36,8 @@ import { TokensStore, MultilineTokens, countEOL, MultilineTokens2, TokensStore2
import { Color } from 'vs/base/common/color';
import { Constants } from 'vs/base/common/uint';
import { EditorTheme } from 'vs/editor/common/view/viewContext';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
function createTextBufferBuilder() {
return new PieceTreeTextBufferBuilder();
@@ -188,7 +190,7 @@ export class TextModel extends Disposable implements model.ITextModel {
};
public static createFromString(text: string, options: model.ITextModelCreationOptions = TextModel.DEFAULT_CREATION_OPTIONS, languageIdentifier: LanguageIdentifier | null = null, uri: URI | null = null): TextModel {
return new TextModel(text, options, languageIdentifier, uri);
return new TextModel(text, options, languageIdentifier, uri, new UndoRedoService());
}
public static resolveOptions(textBuffer: model.ITextBuffer, options: model.ITextModelCreationOptions): model.TextModelResolvedOptions {
@@ -253,6 +255,7 @@ export class TextModel extends Disposable implements model.ITextModel {
public readonly id: string;
public readonly isForSimpleWidget: boolean;
private readonly _associatedResource: URI;
private readonly _undoRedoService: IUndoRedoService;
private _attachedEditorCount: number;
private _buffer: model.ITextBuffer;
private _options: model.TextModelResolvedOptions;
@@ -268,7 +271,7 @@ export class TextModel extends Disposable implements model.ITextModel {
private readonly _isTooLargeForTokenization: boolean;
//#region Editing
private _commandManager: EditStack;
private readonly _commandManager: EditStack;
private _isUndoing: boolean;
private _isRedoing: boolean;
private _trimAutoWhitespaceLines: number[] | null;
@@ -293,7 +296,13 @@ export class TextModel extends Disposable implements model.ITextModel {
private readonly _tokenization: TextModelTokenization;
//#endregion
constructor(source: string | model.ITextBufferFactory, creationOptions: model.ITextModelCreationOptions, languageIdentifier: LanguageIdentifier | null, associatedResource: URI | null = null) {
constructor(
source: string | model.ITextBufferFactory,
creationOptions: model.ITextModelCreationOptions,
languageIdentifier: LanguageIdentifier | null,
associatedResource: URI | null = null,
undoRedoService: IUndoRedoService
) {
super();
// Generate a new unique model id
@@ -305,6 +314,7 @@ export class TextModel extends Disposable implements model.ITextModel {
} else {
this._associatedResource = associatedResource;
}
this._undoRedoService = undoRedoService;
this._attachedEditorCount = 0;
this._buffer = createTextBuffer(source, creationOptions.defaultEOL);
@@ -347,7 +357,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._decorations = Object.create(null);
this._decorationsTree = new DecorationsTrees();
this._commandManager = new EditStack(this);
this._commandManager = new EditStack(this, undoRedoService);
this._isUndoing = false;
this._isRedoing = false;
this._trimAutoWhitespaceLines = null;
@@ -362,6 +372,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._onWillDispose.fire();
this._languageRegistryListener.dispose();
this._tokenization.dispose();
this._undoRedoService.removeElements(this.uri);
this._isDisposed = true;
super.dispose();
this._isDisposing = false;
@@ -436,7 +447,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._decorationsTree = new DecorationsTrees();
// Destroy my edit history and settings
this._commandManager = new EditStack(this);
this._commandManager.clear();
this._trimAutoWhitespaceLines = null;
this._emitContentChangedEvent(
@@ -483,6 +494,21 @@ export class TextModel extends Disposable implements model.ITextModel {
);
}
_setEOL(eol: model.EndOfLineSequence, isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): void {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
this._isUndoing = isUndoing;
this._isRedoing = isRedoing;
this.setEOL(eol);
this._overwriteAlternativeVersionId(resultingAlternativeVersionId);
} finally {
this._isUndoing = false;
this._eventEmitter.endDeferredEmit(resultingSelection);
this._onDidChangeDecorations.endDeferredEmit();
}
}
private _onBeforeEOLChange(): void {
// Ensure all decorations get their `range` set.
const versionId = this.getVersionId();
@@ -1272,18 +1298,37 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer);
}
_applyEdits(edits: model.IValidEditOperations[], isUndoing: boolean, isRedoing: boolean, resultingAlternativeVersionId: number, resultingSelection: Selection[] | null): model.IValidEditOperations[] {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
this._isUndoing = isUndoing;
this._isRedoing = isRedoing;
let reverseEdits: model.IValidEditOperations[] = [];
for (let i = 0, len = edits.length; i < len; i++) {
reverseEdits[i] = { operations: this.applyEdits(edits[i].operations) };
}
this._overwriteAlternativeVersionId(resultingAlternativeVersionId);
return reverseEdits;
} finally {
this._isUndoing = false;
this._eventEmitter.endDeferredEmit(resultingSelection);
this._onDidChangeDecorations.endDeferredEmit();
}
}
public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
return this._applyEdits(this._validateEditOperations(rawOperations));
return this._doApplyEdits(this._validateEditOperations(rawOperations));
} finally {
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
}
private _applyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
const oldLineCount = this._buffer.getLineCount();
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace);
@@ -1364,62 +1409,20 @@ export class TextModel extends Disposable implements model.ITextModel {
return result.reverseEdits;
}
private _undo(): Selection[] | null {
this._isUndoing = true;
let r = this._commandManager.undo();
this._isUndoing = false;
if (!r) {
return null;
}
this._overwriteAlternativeVersionId(r.recordedVersionId);
return r.selections;
}
public undo(): Selection[] | null {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
return this._undo();
} finally {
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
public undo(): void {
this._undoRedoService.undo(this.uri);
}
public canUndo(): boolean {
return this._commandManager.canUndo();
return this._undoRedoService.canUndo(this.uri);
}
private _redo(): Selection[] | null {
this._isRedoing = true;
let r = this._commandManager.redo();
this._isRedoing = false;
if (!r) {
return null;
}
this._overwriteAlternativeVersionId(r.recordedVersionId);
return r.selections;
}
public redo(): Selection[] | null {
try {
this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit();
return this._redo();
} finally {
this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit();
}
public redo(): void {
this._undoRedoService.redo(this.uri);
}
public canRedo(): boolean {
return this._commandManager.canRedo();
return this._undoRedoService.canRedo(this.uri);
}
//#endregion
@@ -3191,10 +3194,11 @@ export class DidChangeContentEmitter extends Disposable {
this._deferredCnt++;
}
public endDeferredEmit(): void {
public endDeferredEmit(resultingSelection: Selection[] | null = null): void {
this._deferredCnt--;
if (this._deferredCnt === 0) {
if (this._deferredEvent !== null) {
this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection;
const e = this._deferredEvent;
this._deferredEvent = null;
this._fastEmitter.fire(e);
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { IRange } from 'vs/editor/common/core/range';
import { Selection } from 'vs/editor/common/core/selection';
/**
* An event describing that the current mode associated with a model has changed.
@@ -225,11 +226,14 @@ export class ModelRawContentChangedEvent {
*/
public readonly isRedoing: boolean;
public resultingSelection: Selection[] | null;
constructor(changes: ModelRawChange[], versionId: number, isUndoing: boolean, isRedoing: boolean) {
this.changes = changes;
this.versionId = versionId;
this.isUndoing = isUndoing;
this.isRedoing = isRedoing;
this.resultingSelection = null;
}
public containsEvent(type: RawContentChangedType): boolean {
@@ -25,6 +25,7 @@ import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
export interface IEditorSemanticHighlightingOptions {
enabled?: boolean;
@@ -103,6 +104,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
private readonly _configurationService: IConfigurationService;
private readonly _configurationServiceSubscription: IDisposable;
private readonly _resourcePropertiesService: ITextResourcePropertiesService;
private readonly _undoRedoService: IUndoRedoService;
private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
public readonly onModelAdded: Event<ITextModel> = this._onModelAdded.event;
@@ -126,11 +128,13 @@ export class ModelServiceImpl extends Disposable implements IModelService {
@IConfigurationService configurationService: IConfigurationService,
@ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService,
@IThemeService themeService: IThemeService,
@ILogService logService: ILogService
@ILogService logService: ILogService,
@IUndoRedoService undoRedoService: IUndoRedoService
) {
super();
this._configurationService = configurationService;
this._resourcePropertiesService = resourcePropertiesService;
this._undoRedoService = undoRedoService;
this._models = {};
this._modelCreationOptionsByLanguageAndResource = Object.create(null);
@@ -272,7 +276,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
// create & save the model
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget);
const model: TextModel = new TextModel(value, options, languageIdentifier, resource);
const model: TextModel = new TextModel(value, options, languageIdentifier, resource, this._undoRedoService);
const modelId = MODEL_ID(model.uri);
if (this._models[modelId]) {
+1
View File
@@ -195,6 +195,7 @@ export const enum VerticalRevealType {
Top = 3,
Bottom = 4,
NearTop = 5,
NearTopIfOutsideViewport = 6,
}
export class ViewRevealRangeRequestEvent {
@@ -33,6 +33,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
private readonly configuration: IConfiguration;
private readonly model: ITextModel;
private readonly _tokenizeViewportSoon: RunOnceScheduler;
private readonly _updateConfigurationViewLineCount: RunOnceScheduler;
private hasFocus: boolean;
private viewportStartLine: number;
private viewportStartLineTrackedRange: string | null;
@@ -56,6 +57,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this.configuration = configuration;
this.model = model;
this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50));
this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0));
this.hasFocus = false;
this.viewportStartLine = -1;
this.viewportStartLineTrackedRange = null;
@@ -130,6 +132,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this._endEmit();
}
}));
this._updateConfigurationViewLineCountNow();
}
public dispose(): void {
@@ -142,6 +146,10 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
}
private _updateConfigurationViewLineCountNow(): void {
this.configuration.setViewLineCount(this.lines.getViewLineCount());
}
public tokenizeViewport(): void {
const linesViewportData = this.viewLayout.getLinesViewportData();
const startPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(linesViewportData.startLineNumber, 1));
@@ -180,6 +188,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
// Never change the scroll position from 0 to something else...
restorePreviousViewportStart = true;
}
this._updateConfigurationViewLineCount.schedule();
}
if (e.hasChanged(EditorOption.readOnly)) {
@@ -301,6 +311,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
// Update the configuration and reset the centered view line
this.viewportStartLine = -1;
this.configuration.setMaxLineNumber(this.model.getLineCount());
this._updateConfigurationViewLineCountNow();
// Recover viewport
if (!this.hasFocus && this.model.getAttachedEditorCount() >= 2 && this.viewportStartLineTrackedRange) {
@@ -358,6 +369,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
} finally {
this._endEmit();
}
this._updateConfigurationViewLineCount.schedule();
}
}));
@@ -387,6 +399,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
} finally {
this._endEmit();
}
this._updateConfigurationViewLineCount.schedule();
}
public getVisibleRanges(): Range[] {
@@ -15,6 +15,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { FindModelBoundToEditorModel } from 'vs/editor/contrib/find/findModel';
import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
suite('FindModel', () => {
@@ -44,7 +45,7 @@ suite('FindModel', () => {
const factory = ptBuilder.finish();
withTestCodeEditor([],
{
model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null)
model: new TextModel(factory, TextModel.DEFAULT_CREATION_OPTIONS, null, null, new UndoRedoService())
},
(editor, cursor) => callback(editor as unknown as IActiveCodeEditor, cursor)
);
+1 -1
View File
@@ -885,7 +885,7 @@ for (let i = 1; i <= 7; i++) {
);
}
export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('foldBackgroundBackground', "Background color behind folded ranges."));
export const foldBackgroundBackground = registerColor('editor.foldBackground', { light: transparent(editorSelectionBackground, 0.3), dark: transparent(editorSelectionBackground, 0.3), hc: null }, nls.localize('foldBackgroundBackground', "Background color behind folded ranges. The color must not be opaque so as not to hide underlying decorations."), true);
registerThemingParticipant((theme, collector) => {
const foldBackground = theme.getColor(foldBackgroundBackground);
@@ -167,7 +167,7 @@ abstract class SymbolNavigationAction extends EditorAction {
resource: reference.uri,
options: {
selection: Range.collapseToStart(range),
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport
selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport
}
}, editor, sideBySide);
@@ -128,7 +128,7 @@ class SymbolNavigationService implements ISymbolNavigationService {
resource: reference.uri,
options: {
selection: Range.collapseToStart(reference.range),
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport
selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport
}
}, source).finally(() => {
this._ignoreEditorChange = false;
@@ -317,7 +317,7 @@ suite('Editor Contrib - Line Operations', () => {
assert.equal(model.getLineContent(1), 'one');
assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1));
editor.trigger('keyboard', Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Typing some text here on line one');
assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31));
});
@@ -447,7 +447,7 @@ suite('Editor Contrib - Line Operations', () => {
assert.equal(model.getLineContent(1), 'hello my dear world');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
editor.trigger('keyboard', Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'hello my dear');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14));
});
@@ -815,13 +815,13 @@ suite('Editor Contrib - Line Operations', () => {
new Selection(2, 4, 2, 4)
]);
editor.trigger('tests', Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4)
]);
editor.trigger('tests', Handler.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4)
@@ -19,6 +19,7 @@ import { WordSelectionRangeProvider } from 'vs/editor/contrib/smartSelect/wordSe
import { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { NullLogService } from 'vs/platform/log/common/log';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
class MockJSMode extends MockMode {
@@ -47,7 +48,7 @@ suite('SmartSelect', () => {
setup(() => {
const configurationService = new TestConfigurationService();
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService());
modelService = new ModelServiceImpl(configurationService, new TestTextResourcePropertiesService(configurationService), new TestThemeService(), new NullLogService(), new UndoRedoService());
mode = new MockJSMode();
});
@@ -11,6 +11,7 @@ import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { Emitter } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event';
import { domContentLoaded } from 'vs/base/browser/dom';
export class SuggestRangeHighlighter {
@@ -101,10 +102,12 @@ const shiftKey = new class ShiftKey extends Emitter<boolean> {
constructor() {
super();
this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey));
this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false));
domContentLoaded().then(() => {
this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey));
this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'blur')(() => this.isPressed = false));
});
}
get isPressed(): boolean {
@@ -13,6 +13,7 @@ import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorW
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { Handler } from 'vs/editor/common/editorCommon';
import { Cursor } from 'vs/editor/common/controller/cursor';
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
suite('WordOperations', () => {
@@ -216,7 +217,7 @@ suite('WordOperations', () => {
assert.equal(editor.getValue(), 'foo qbar baz');
cursorCommand(cursor, Handler.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(editor.getValue(), 'foo bar baz');
});
});
@@ -50,6 +50,8 @@ import { getSingletonServiceDescriptors } from 'vs/platform/instantiation/common
import { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/clipboardService';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
export interface IEditorOverrideServices {
[index: string]: any;
@@ -150,7 +152,9 @@ export module StaticServices {
export const logService = define(ILogService, () => new NullLogService());
export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o)));
export const undoRedoService = define(IUndoRedoService, () => new UndoRedoService());
export const modelService = define(IModelService, (o) => new ModelServiceImpl(configurationService.get(o), resourcePropertiesService.get(o), standaloneThemeService.get(o), logService.get(o), undoRedoService.get(o)));
export const markerDecorationsService = define(IMarkerDecorationsService, (o) => new MarkerDecorationsService(modelService.get(o), markerService.get(o)));
@@ -1240,22 +1240,22 @@ suite('Editor Controller - Regression tests', () => {
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert9');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert10');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert11');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\n\tx', 'assert12');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\t\nx', 'assert13');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert14');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert15');
});
@@ -1263,12 +1263,12 @@ suite('Editor Controller - Regression tests', () => {
});
test('issue #23539: Setting model EOL isn\'t undoable', () => {
usingCursor({
text: [
'Hello',
'world'
]
}, (model, cursor) => {
withTestCodeEditor([
'Hello',
'world'
], {}, (editor, cursor) => {
const model = editor.getModel()!;
assertCursor(cursor, new Position(1, 1));
model.setEOL(EndOfLineSequence.LF);
assert.equal(model.getValue(), 'Hello\nworld');
@@ -1276,7 +1276,7 @@ suite('Editor Controller - Regression tests', () => {
model.pushEOL(EndOfLineSequence.CRLF);
assert.equal(model.getValue(), 'Hello\r\nworld');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), 'Hello\nworld');
});
});
@@ -1301,7 +1301,7 @@ suite('Editor Controller - Regression tests', () => {
cursorCommand(cursor, H.Type, { text: '%' }, 'keyboard');
assert.equal(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2');
});
@@ -1327,39 +1327,39 @@ suite('Editor Controller - Regression tests', () => {
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello');
assertCursor(cursor, new Position(1, 6));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), '');
assertCursor(cursor, new Position(1, 1));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello');
assertCursor(cursor, new Position(1, 6));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12));
});
@@ -1735,21 +1735,21 @@ suite('Editor Controller - Regression tests', () => {
'\t just some text'
].join('\n'), '001');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [
' some lines',
' and more lines',
' just some text',
].join('\n'), '002');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [
'some lines',
'and more lines',
'just some text',
].join('\n'), '003');
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [
'some lines',
'and more lines',
@@ -1935,10 +1935,8 @@ suite('Editor Controller - Regression tests', () => {
});
test('issue #9675: Undo/Redo adds a stop in between CHN Characters', () => {
usingCursor({
text: [
]
}, (model, cursor) => {
withTestCodeEditor([], {}, (editor, cursor) => {
const model = editor.getModel()!;
assertCursor(cursor, new Position(1, 1));
// Typing sennsei in Japanese - Hiragana
@@ -1957,7 +1955,7 @@ suite('Editor Controller - Regression tests', () => {
assert.equal(model.getLineContent(1), 'せんせい');
assertCursor(cursor, new Position(1, 5));
cursorCommand(cursor, H.Undo);
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), '');
assertCursor(cursor, new Position(1, 1));
});
@@ -2138,7 +2136,7 @@ suite('Editor Controller - Regression tests', () => {
}], () => [new Selection(1, 1, 1, 1)]);
assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!');
});
@@ -2229,12 +2227,12 @@ suite('Editor Controller - Regression tests', () => {
new Selection(1, 5, 1, 5),
]);
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assertCursor(cursor, [
new Selection(1, 4, 1, 4),
]);
cursorCommand(cursor, H.Redo, null, 'keyboard');
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assertCursor(cursor, [
new Selection(1, 5, 1, 5),
]);
@@ -2263,7 +2261,7 @@ suite('Editor Controller - Regression tests', () => {
new Selection(1, 1, 1, 1),
]);
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assertCursor(cursor, [
new Selection(1, 1, 1, 1),
]);
@@ -2378,49 +2376,49 @@ suite('Editor Controller - Cursor Configuration', () => {
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 1) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 2
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 2) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'M y Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 3
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 3) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 4
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 4) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 5
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My S econd Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 5
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My S econd Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 13
assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 13) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Li ne123');
cursorCommand(cursor, H.Undo, null, 'keyboard');
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 14
assert.equal(model.getLineContent(2), 'My Second Line123');
@@ -2774,7 +2772,7 @@ suite('Editor Controller - Cursor Configuration', () => {
assert.equal(model.getLineContent(2), 'a ');
// Undo DeleteLeft - get us back to original indentation
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' a ');
// Nothing is broken when cursor is in (1,1)
@@ -2859,22 +2857,22 @@ suite('Editor Controller - Cursor Configuration', () => {
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert10');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert11');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert12');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\n\tx', 'assert13');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\n\ty\nx', 'assert14');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\nx', 'assert15');
cursorCommand(cursor, H.Redo, {});
CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert16');
});
@@ -2895,7 +2893,7 @@ suite('Editor Controller - Cursor Configuration', () => {
const beforeVersion = model.getVersionId();
const beforeAltVersion = model.getAlternativeVersionId();
cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard');
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
const afterVersion = model.getVersionId();
const afterAltVersion = model.getAlternativeVersionId();
@@ -4263,7 +4261,7 @@ suite('autoClosingPairs', () => {
moveTo(cursor, lineNumber, column);
cursorCommand(cursor, H.Type, { text: chr }, 'keyboard');
assert.deepEqual(model.getLineContent(lineNumber), expected, message);
cursorCommand(cursor, H.Undo);
model.undo();
}
test('open parens: default', () => {
@@ -5347,11 +5345,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A fir line');
assertCursor(cursor, new Selection(1, 6, 1, 6));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3));
});
@@ -5376,11 +5374,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A firstine');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3));
});
@@ -5410,11 +5408,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'Second line');
assertCursor(cursor, new Selection(2, 7, 2, 7));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' line');
assertCursor(cursor, new Selection(2, 1, 2, 1));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 8, 2, 8));
});
@@ -5448,11 +5446,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), '');
assertCursor(cursor, new Selection(2, 1, 2, 1));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' line');
assertCursor(cursor, new Selection(2, 1, 2, 1));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 8, 2, 8));
});
@@ -5479,11 +5477,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'Another text');
assertCursor(cursor, new Selection(2, 13, 2, 13));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another ');
assertCursor(cursor, new Selection(2, 9, 2, 9));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 9, 2, 9));
});
@@ -5515,11 +5513,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'An');
assertCursor(cursor, new Selection(2, 3, 2, 3));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another ');
assertCursor(cursor, new Selection(2, 9, 2, 9));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 9, 2, 9));
});
@@ -5539,15 +5537,15 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A first and interesting line');
assertCursor(cursor, new Selection(1, 24, 1, 24));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first and line');
assertCursor(cursor, new Selection(1, 12, 1, 12));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8));
cursorCommand(cursor, H.Undo, {});
CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3));
});
@@ -84,6 +84,7 @@ export function withTestCodeEditor(text: string | string[] | null, options: Test
}
let editor = <TestCodeEditor>createTestCodeEditor(options);
editor.getCursor()!.setHasFocus(true);
callback(editor, editor.getCursor()!);
editor.dispose();
@@ -12,7 +12,7 @@ import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvent
import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils';
function createEditableTextModelFromString(text: string): TextModel {
return new TextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null);
return TextModel.createFromString(text, TextModel.DEFAULT_CREATION_OPTIONS, null);
}
suite('EditorModel - EditableTextModel.applyEdits updates mightContainRTL', () => {
@@ -88,7 +88,7 @@ function assertLineMapping(model: TextModel, msg: string): void {
export function assertSyncedModels(text: string, callback: (model: TextModel, assertMirrorModels: () => void) => void, setup: ((model: TextModel) => void) | null = null): void {
let model = new TextModel(text, TextModel.DEFAULT_CREATION_OPTIONS, null);
let model = TextModel.createFromString(text, TextModel.DEFAULT_CREATION_OPTIONS, null);
model.setEOL(EndOfLineSequence.LF);
assertLineMapping(model, 'model');
@@ -106,7 +106,7 @@ suite('ModelLinesTokens', () => {
function testApplyEdits(initial: IBufferLineState[], edits: IEdit[], expected: IBufferLineState[]): void {
const initialText = initial.map(el => el.text).join('\n');
const model = new TextModel(initialText, TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0));
const model = TextModel.createFromString(initialText, TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0));
for (let lineIndex = 0; lineIndex < initial.length; lineIndex++) {
const lineTokens = initial[lineIndex].tokens;
const lineTextLength = model.getLineMaxColumn(lineIndex + 1) - 1;
@@ -442,7 +442,7 @@ suite('ModelLinesTokens', () => {
}
test('insertion on empty line', () => {
const model = new TextModel('some text', TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0));
const model = TextModel.createFromString('some text', TextModel.DEFAULT_CREATION_OPTIONS, new LanguageIdentifier('test', 0));
const tokens = TestToken.toTokens([new TestToken(0, 1)]);
LineTokens.convertToEndOffset(tokens, model.getLineMaxColumn(1) - 1);
model.setLineTokens(1, tokens);
@@ -72,7 +72,7 @@ suite('TextModelWithTokens', () => {
brackets: brackets
});
let model = new TextModel(
let model = TextModel.createFromString(
contents.join('\n'),
TextModel.DEFAULT_CREATION_OPTIONS,
languageIdentifier
@@ -18,6 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { NullLogService } from 'vs/platform/log/common/log';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
const GENERATE_TESTS = false;
@@ -29,7 +30,7 @@ suite('ModelService', () => {
configService.setUserConfiguration('files', { 'eol': '\n' });
configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot'));
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService());
modelService = new ModelServiceImpl(configService, new TestTextResourcePropertiesService(configService), new TestThemeService(), new NullLogService(), new UndoRedoService());
});
teardown(() => {
@@ -80,7 +80,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
outerWidth: input.outerWidth,
outerHeight: input.outerHeight,
lineHeight: input.lineHeight,
maxLineNumber: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1,
viewLineCount: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1,
lineNumbersDigitCount: input.lineNumbersDigitCount,
typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth,
maxDigitWidth: input.maxDigitWidth,
+5 -1
View File
@@ -923,7 +923,11 @@ var AMDLoader;
var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest();
if (!hashData.equals(hashDataNow)) {
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); });
_this._fs.unlink(cachedDataPath, function (err) {
if (err) {
moduleManager.getConfig().onError(err);
}
});
}
}, Math.ceil(5000 * (1 + Math.random())));
};
+5
View File
@@ -2307,6 +2307,11 @@ declare namespace monaco.editor {
* optimized for viewing a code definition.
*/
revealRangeNearTop(range: IRange, scrollType?: ScrollType): void;
/**
* Scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport,
* optimized for viewing a code definition. Only if it lies outside the viewport.
*/
revealRangeNearTopIfOutsideViewport(range: IRange, scrollType?: ScrollType): void;
/**
* Directly trigger a handler or an editor action.
* @param source The source of the call.
@@ -3,11 +3,24 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IUserDataAuthTokenService } from 'vs/platform/userDataSync/common/userDataSync';
export class UserDataAuthTokenService extends Disposable implements IUserDataAuthTokenService {
export const IAuthenticationTokenService = createDecorator<IAuthenticationTokenService>('IAuthenticationTokenService');
export interface IAuthenticationTokenService {
_serviceBrand: undefined;
readonly onDidChangeToken: Event<string | undefined>;
readonly onTokenFailed: Event<void>;
getToken(): Promise<string | undefined>;
setToken(accessToken: string | undefined): Promise<void>;
sendTokenFailed(): void;
}
export class AuthenticationTokenService extends Disposable implements IAuthenticationTokenService {
_serviceBrand: any;
@@ -38,3 +51,4 @@ export class UserDataAuthTokenService extends Disposable implements IUserDataAut
this._onTokenFailed.fire();
}
}
@@ -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.
*--------------------------------------------------------------------------------------------*/
import { IServerChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class AuthenticationTokenServiceChannel implements IServerChannel {
constructor(private readonly service: IAuthenticationTokenService) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidChangeToken': return this.service.onDidChangeToken;
case 'onTokenFailed': return this.service.onTokenFailed;
}
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'setToken': return this.service.setToken(args);
case 'getToken': return this.service.getToken();
}
throw new Error('Invalid call');
}
}
@@ -113,6 +113,7 @@ export interface IConfigurationPropertySchema extends IJSONSchema {
scope?: ConfigurationScope;
included?: boolean;
tags?: string[];
disallowSyncIgnore?: boolean;
}
export interface IConfigurationExtensionInfo {
+5
View File
@@ -228,6 +228,11 @@ export const enum TextEditorSelectionRevealType {
* Option to scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, but not quite at the top.
*/
NearTop = 2,
/**
* Option to scroll vertically or horizontally as necessary and reveal a range close to the top of the viewport, but not quite at the top.
* Only if it lies outside the viewport
*/
NearTopIfOutsideViewport = 3,
}
export interface ITextEditorOptions extends IEditorOptions {
+2
View File
@@ -59,6 +59,8 @@ export interface IssueReporterData extends WindowData {
enabledExtensions: IssueReporterExtensionData[];
issueType?: IssueType;
extensionId?: string;
readonly issueTitle?: string;
readonly issueBody?: string;
}
export interface ISettingSearchResult {
@@ -35,6 +35,7 @@ export enum RemoteAuthorityResolverErrorCode {
Unknown = 'Unknown',
NotAvailable = 'NotAvailable',
TemporarilyNotAvailable = 'TemporarilyNotAvailable',
NoResolverFound = 'NoResolverFound'
}
export class RemoteAuthorityResolverError extends Error {
@@ -50,10 +51,11 @@ export class RemoteAuthorityResolverError extends Error {
}
public static isTemporarilyNotAvailable(err: any): boolean {
if (err instanceof RemoteAuthorityResolverError) {
return err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable;
}
return false;
return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable;
}
public static isNoResolverFound(err: any): boolean {
return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.NoResolverFound;
}
public readonly _message: string | undefined;
+2 -2
View File
@@ -16,7 +16,7 @@ export interface IUndoRedoElement {
/**
* None, one or multiple resources that this undo/redo element impacts.
*/
readonly resources: URI[];
readonly resources: readonly URI[];
/**
* The label of the undo/redo element.
@@ -43,7 +43,7 @@ export interface IUndoRedoElement {
* Invalidate the edits concerning `resource`.
* i.e. the undo/redo stack for that particular resource has been destroyed.
*/
invalidate(resource: URI): boolean;
invalidate(resource: URI): void;
}
export interface IUndoRedoService {
@@ -7,11 +7,12 @@ import { IUndoRedoService, IUndoRedoElement } from 'vs/platform/undoRedo/common/
import { URI } from 'vs/base/common/uri';
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
import { onUnexpectedError } from 'vs/base/common/errors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
class StackElement {
public readonly actual: IUndoRedoElement;
public readonly label: string;
public readonly resources: URI[];
public readonly resources: readonly URI[];
public readonly strResources: string[];
constructor(actual: IUndoRedoElement) {
@@ -179,7 +180,7 @@ export class UndoRedoService implements IUndoRedoService {
return false;
}
redo(resource: URI): void {
public redo(resource: URI): void {
const strResource = uriGetComparisonKey(resource);
if (!this._editStacks.has(strResource)) {
return;
@@ -239,3 +240,5 @@ export class UndoRedoService implements IUndoRedoService {
}
}
}
registerSingleton(IUndoRedoService, UndoRedoService);
@@ -19,6 +19,8 @@ import { FormattingOptions } from 'vs/base/common/jsonFormatter';
import { IStringDictionary } from 'vs/base/common/collections';
import { localize } from 'vs/nls';
const BACK_UP_MAX_AGE = 1000 * 60 * 60 * 24 * 30; /* 30 days */
type SyncSourceClassification = {
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
};
@@ -68,6 +70,7 @@ export abstract class AbstractSynchroniser extends Disposable {
this.syncFolder = joinPath(environmentService.userDataSyncHome, source);
this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`);
this.cleanUpDelayer = new ThrottledDelayer(50);
this.cleanUpBackup();
}
protected setStatus(status: SyncStatus): void {
@@ -195,9 +198,24 @@ export abstract class AbstractSynchroniser extends Disposable {
private async cleanUpBackup(): Promise<void> {
const stat = await this.fileService.resolve(this.syncFolder);
if (stat.children) {
const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort();
const toDelete = all.slice(0, Math.max(0, all.length - 9));
await Promise.all(toDelete.map(stat => this.fileService.del(stat.resource)));
const toDelete = stat.children.filter(stat => {
if (stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)) {
const ctime = stat.ctime || new Date(
parseInt(stat.name.substring(0, 4)),
parseInt(stat.name.substring(4, 6)) - 1,
parseInt(stat.name.substring(6, 8)),
parseInt(stat.name.substring(9, 11)),
parseInt(stat.name.substring(11, 13)),
parseInt(stat.name.substring(13, 15))
).getTime();
return Date.now() - ctime > BACK_UP_MAX_AGE;
}
return false;
});
await Promise.all(toDelete.map(stat => {
this.logService.info('Deleting from backup', stat.resource.path);
this.fileService.del(stat.resource);
}));
}
}
@@ -29,6 +29,12 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
};
}
// massage incoming extension - add disabled property
const massageIncomingExtension = (extension: ISyncExtension): ISyncExtension => ({ ...extension, ...{ disabled: !!extension.disabled } });
localExtensions = localExtensions.map(massageIncomingExtension);
remoteExtensions = remoteExtensions.map(massageIncomingExtension);
lastSyncExtensions = lastSyncExtensions ? lastSyncExtensions.map(massageIncomingExtension) : null;
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));
@@ -37,10 +43,12 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier));
}
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
const getKey = (extension: ISyncExtension): string => {
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 uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
};
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
map.set(getKey(extension), extension);
return map;
};
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>());
@@ -62,14 +70,17 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
const baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, ignoredExtensionsSet);
const massageSyncExtension = (extension: ISyncExtension, key: string): ISyncExtension => {
// massage outgoing extension - remove disabled property
const massageOutgoingExtension = (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.disabled) {
massagedExtension.disabled = true;
}
if (extension.version) {
massagedExtension.version = extension.version;
}
@@ -90,25 +101,25 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
if (baseToLocal.added.has(key)) {
// Is different from local to remote
if (localToRemote.updated.has(key)) {
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
}
} else {
// Add to local
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
added.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
}
}
// Remotely updated extensions
for (const key of values(baseToRemote.updated)) {
// Update in local always
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key));
updated.push(massageOutgoingExtension(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));
newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!);
}
}
@@ -121,7 +132,7 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
// If not updated in remote
if (!baseToRemote.updated.has(key)) {
newRemoteExtensionsMap.set(key, massageSyncExtension(localExtensionsMap.get(key)!, key));
newRemoteExtensionsMap.set(key, localExtensionsMap.get(key)!);
}
}
@@ -133,9 +144,13 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
}
}
const remote: ISyncExtension[] = [];
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 };
if (remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0) {
newRemoteExtensionsMap.forEach((value, key) => remote.push(massageOutgoingExtension(value, key)));
}
return { added, removed, updated, remote: remote.length ? remote : null };
}
function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISyncExtension>, ignoredExtensions: Set<string>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
@@ -152,7 +167,7 @@ function compare(from: Map<string, ISyncExtension> | null, to: Map<string, ISync
const fromExtension = from!.get(key)!;
const toExtension = to.get(key);
if (!toExtension
|| fromExtension.enabled !== toExtension.enabled
|| fromExtension.disabled !== toExtension.disabled
|| fromExtension.version !== toExtension.version
) {
updated.add(key);
@@ -14,17 +14,19 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { localize } from 'vs/nls';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
import { isNonEmptyArray } from 'vs/base/common/arrays';
import { AbstractSynchroniser, IRemoteUserData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { AbstractSynchroniser, IRemoteUserData, ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { VSBuffer } from 'vs/base/common/buffer';
interface ISyncPreviewResult {
readonly localExtensions: ISyncExtension[];
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: ILastSyncUserData | null;
readonly added: ISyncExtension[];
readonly removed: IExtensionIdentifier[];
readonly updated: ISyncExtension[];
readonly remote: ISyncExtension[] | null;
readonly remoteUserData: IRemoteUserData;
readonly skippedExtensions: ISyncExtension[];
readonly lastSyncUserData: ILastSyncUserData | null;
}
interface ILastSyncUserData extends IRemoteUserData {
@@ -34,7 +36,7 @@ interface ILastSyncUserData extends IRemoteUserData {
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
readonly resourceKey: ResourceKey = 'extensions';
protected readonly version: number = 1;
protected readonly version: number = 2;
constructor(
@IEnvironmentService environmentService: IEnvironmentService,
@@ -75,9 +77,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (remoteUserData.syncData !== null) {
const localExtensions = await this.getLocalExtensions();
const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.syncData.content);
const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions());
await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData });
const remoteExtensions = this.parseExtensions(remoteUserData.syncData);
const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions());
await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData });
}
// No remote exists to pull
@@ -107,7 +109,7 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions());
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
await this.apply({ added, removed, updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData }, true);
await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData }, true);
this.logService.info('Extensions: Finished pushing extensions.');
} finally {
@@ -165,8 +167,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> {
const remoteExtensions: ISyncExtension[] = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.syncData!.content) : null;
const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? this.parseExtensions(remoteUserData.syncData) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? this.parseExtensions(lastSyncUserData.syncData!) : null;
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
const localExtensions = await this.getLocalExtensions();
@@ -179,14 +181,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const { added, removed, updated, remote } = merge(localExtensions, remoteExtensions, lastSyncExtensions, skippedExtensions, this.getIgnoredExtensions());
return { added, removed, updated, remote, skippedExtensions, remoteUserData, lastSyncUserData };
return { added, removed, updated, remote, skippedExtensions, remoteUserData, localExtensions, lastSyncUserData };
}
private getIgnoredExtensions() {
return this.configurationService.getValue<string[]>('sync.ignoredExtensions') || [];
}
private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
private async apply({ added, removed, updated, remote, remoteUserData, skippedExtensions, lastSyncUserData, localExtensions }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
const hasChanges = added.length || removed.length || updated.length || remote;
@@ -195,6 +197,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
}
if (added.length || removed.length || updated.length) {
// back up all disabled or market place extensions
const backUpExtensions = localExtensions.filter(e => e.disabled || !!e.identifier.uuid);
await this.backupLocal(VSBuffer.fromString(JSON.stringify(backUpExtensions)));
skippedExtensions = await this.updateLocalExtensions(added, removed, updated, skippedExtensions);
}
@@ -236,14 +241,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
// Builtin Extension: Sync only enablement state
if (installedExtension && installedExtension.type === ExtensionType.System) {
if (e.enabled) {
this.logService.trace('Extensions: Enabling extension...', e.identifier.id);
await this.extensionEnablementService.enableExtension(e.identifier);
this.logService.info('Extensions: Enabled extension', e.identifier.id);
} else {
if (e.disabled) {
this.logService.trace('Extensions: Disabling extension...', e.identifier.id);
await this.extensionEnablementService.disableExtension(e.identifier);
this.logService.info('Extensions: Disabled extension', e.identifier.id);
} else {
this.logService.trace('Extensions: Enabling extension...', e.identifier.id);
await this.extensionEnablementService.enableExtension(e.identifier);
this.logService.info('Extensions: Enabled extension', e.identifier.id);
}
removeFromSkipped.push(e.identifier);
return;
@@ -252,14 +257,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version);
if (extension) {
try {
if (e.enabled) {
this.logService.trace('Extensions: Enabling extension...', e.identifier.id, extension.version);
await this.extensionEnablementService.enableExtension(extension.identifier);
this.logService.info('Extensions: Enabled extension', e.identifier.id, extension.version);
} else {
if (e.disabled) {
this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version);
await this.extensionEnablementService.disableExtension(extension.identifier);
this.logService.info('Extensions: Disabled extension', e.identifier.id, extension.version);
} else {
this.logService.trace('Extensions: Enabling extension...', e.identifier.id, extension.version);
await this.extensionEnablementService.enableExtension(extension.identifier);
this.logService.info('Extensions: Enabled extension', e.identifier.id, extension.version);
}
// Install only if the extension does not exist
if (!installedExtension || installedExtension.manifest.version !== extension.version) {
@@ -293,11 +298,33 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return newSkippedExtensions;
}
private parseExtensions(syncData: ISyncData): ISyncExtension[] {
let extensions: ISyncExtension[] = JSON.parse(syncData.content);
if (syncData.version !== this.version) {
extensions = extensions.map(e => {
// #region Migration from v1 (enabled -> disabled)
if (!(<any>e).enabled) {
e.disabled = true;
}
delete (<any>e).enabled;
// #endregion
return e;
});
}
return extensions;
}
private async getLocalExtensions(): Promise<ISyncExtension[]> {
const installedExtensions = await this.extensionManagementService.getInstalled();
const disabledExtensions = await this.extensionEnablementService.getDisabledExtensions();
const disabledExtensions = this.extensionEnablementService.getDisabledExtensions();
return installedExtensions
.map(({ identifier }) => ({ identifier, enabled: !disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier)) }));
.map(({ identifier }) => {
const syncExntesion: ISyncExtension = { identifier };
if (disabledExtensions.some(disabledExtension => areSameExtensions(disabledExtension, identifier))) {
syncExntesion.disabled = true;
}
return syncExntesion;
});
}
}
@@ -21,6 +21,7 @@ const argvProperties: string[] = ['locale'];
interface ISyncPreviewResult {
readonly local: IGlobalState | undefined;
readonly remote: IGlobalState | undefined;
readonly localUserData: IGlobalState;
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IRemoteUserData | null;
}
@@ -59,8 +60,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.syncData !== null) {
const localUserData = await this.getLocalGlobalState();
const local: IGlobalState = JSON.parse(remoteUserData.syncData.content);
await this.apply({ local, remote: undefined, remoteUserData, lastSyncUserData });
await this.apply({ local, remote: undefined, remoteUserData, localUserData, lastSyncUserData });
}
// No remote exists to pull
@@ -86,10 +88,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
this.logService.info('UI State: Started pushing UI State...');
this.setStatus(SyncStatus.Syncing);
const remote = await this.getLocalGlobalState();
const localUserData = await this.getLocalGlobalState();
const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
await this.apply({ local: undefined, remote, remoteUserData, lastSyncUserData }, true);
await this.apply({ local: undefined, remote: localUserData, remoteUserData, localUserData, lastSyncUserData }, true);
this.logService.info('UI State: Finished pushing UI State.');
} finally {
@@ -152,10 +154,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState);
return { local, remote, remoteUserData, lastSyncUserData };
return { local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData };
}
private async apply({ local, remote, remoteUserData, lastSyncUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
const hasChanges = local || remote;
@@ -166,6 +168,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
if (local) {
// update local
this.logService.trace('UI State: Updating local ui state...');
await this.backupLocal(VSBuffer.fromString(JSON.stringify(localUserData)));
await this.writeLocalGlobalState(local);
this.logService.info('UI State: Updated local ui state');
}
@@ -260,9 +260,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
if (content !== null) {
if (this.hasErrors(content)) {
throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source);
}
this.validateContent(content);
if (hasLocalChanged) {
this.logService.trace('Settings: Updating local settings...');
@@ -317,21 +315,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
if (remoteSettingsSyncContent) {
const localContent: string = fileContent ? fileContent.value.toString() : '{}';
// No action when there are errors
if (this.hasErrors(localContent)) {
throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source);
}
else {
this.logService.trace('Settings: Merging remote settings with local settings...');
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions);
content = result.localContent || result.remoteContent;
hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null;
hasConflicts = result.hasConflicts;
conflictSettings = result.conflictsSettings;
}
this.validateContent(localContent);
this.logService.trace('Settings: Merging remote settings with local settings...');
const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions);
content = result.localContent || result.remoteContent;
hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null;
hasConflicts = result.hasConflicts;
conflictSettings = result.conflictsSettings;
}
// First time syncing to remote
@@ -364,4 +355,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
}
return null;
}
private validateContent(content: string): void {
if (this.hasErrors(content)) {
throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source);
}
}
}
@@ -6,7 +6,8 @@
import { timeout, Delayer } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle';
import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAuthTokenService, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, SyncSource, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncLogService, IUserDataSyncService, SyncStatus, IUserDataAutoSyncService, UserDataSyncError, UserDataSyncErrorCode, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService {
@@ -16,19 +17,19 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
private successiveFailures: number = 0;
private readonly syncDelayer: Delayer<void>;
private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>());
readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event;
private readonly _onError: Emitter<UserDataSyncError> = this._register(new Emitter<UserDataSyncError>());
readonly onError: Event<UserDataSyncError> = this._onError.event;
constructor(
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService,
@IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService,
) {
super();
this.updateEnablement(false, true);
this.syncDelayer = this._register(new Delayer<void>(0));
this._register(Event.any<any>(userDataAuthTokenService.onDidChangeToken)(() => this.updateEnablement(true, true)));
this._register(Event.any<any>(authTokenService.onDidChangeToken)(() => this.updateEnablement(true, true)));
this._register(Event.any<any>(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true, true)));
this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => this.updateEnablement(true, false)));
this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync()));
@@ -61,27 +62,23 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
await this.userDataSyncService.sync();
this.resetFailures();
} catch (e) {
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.TurnedOff) {
const error = UserDataSyncError.toUserDataSyncError(e);
if (error.code === UserDataSyncErrorCode.TurnedOff || error.code === UserDataSyncErrorCode.SessionExpired) {
this.logService.info('Auto Sync: Sync is turned off in the cloud.');
this.logService.info('Auto Sync: Resetting the local sync state.');
await this.userDataSyncService.resetLocal();
this.logService.info('Auto Sync: Completed resetting the local sync state.');
if (auto) {
return this.userDataSyncEnablementService.setEnablement(false);
this.userDataSyncEnablementService.setEnablement(false);
this._onError.fire(error);
return;
} else {
return this.sync(loop, auto);
}
}
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.SessionExpired) {
this.logService.info('Auto Sync: Cloud has new session');
this.logService.info('Auto Sync: Resetting the local sync state.');
await this.userDataSyncService.resetLocal();
this.logService.info('Auto Sync: Completed resetting the local sync state.');
return this.sync(loop, auto);
}
this.logService.error(e);
this.logService.error(error);
this.successiveFailures++;
this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown });
this._onError.fire(error);
}
if (loop) {
await timeout(1000 * 60 * 5);
@@ -95,7 +92,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
private async isAutoSyncEnabled(): Promise<boolean> {
return this.userDataSyncEnablementService.isEnabled()
&& this.userDataSyncService.status !== SyncStatus.Uninitialized
&& !!(await this.userDataAuthTokenService.getToken());
&& !!(await this.authTokenService.getToken());
}
private resetFailures(): void {
@@ -38,6 +38,7 @@ export interface ISyncConfiguration {
export function registerConfiguration(): IDisposable {
const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings';
const ignoredExtensionsSchemaId = 'vscode://schemas/ignoredExtensions';
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({
id: 'sync',
@@ -84,14 +85,11 @@ export function registerConfiguration(): IDisposable {
'sync.ignoredExtensions': {
'type': 'array',
'description': localize('sync.ignoredExtensions', "List of extensions to be ignored while synchronizing. The identifier of an extension is always ${publisher}.${name}. For example: vscode.csharp."),
items: {
type: 'string',
pattern: EXTENSION_IDENTIFIER_PATTERN,
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
},
$ref: ignoredExtensionsSchemaId,
'default': [],
'scope': ConfigurationScope.APPLICATION,
uniqueItems: true
uniqueItems: true,
disallowSyncIgnore: true
},
'sync.ignoredSettings': {
'type': 'array',
@@ -100,12 +98,13 @@ export function registerConfiguration(): IDisposable {
'scope': ConfigurationScope.APPLICATION,
$ref: ignoredSettingsSchemaId,
additionalProperties: true,
uniqueItems: true
uniqueItems: true,
disallowSyncIgnore: true
}
}
});
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
const registerIgnoredSettingsSchema = () => {
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
const ignoredSettingsSchema: IJSONSchema = {
items: {
type: 'string',
@@ -114,6 +113,11 @@ export function registerConfiguration(): IDisposable {
};
jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema);
};
jsonRegistry.registerSchema(ignoredExtensionsSchemaId, {
type: 'string',
pattern: EXTENSION_IDENTIFIER_PATTERN,
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
});
return configurationRegistry.onDidUpdateConfiguration(() => registerIgnoredSettingsSchema());
}
@@ -210,7 +214,7 @@ export class UserDataSyncStoreError extends UserDataSyncError { }
export interface ISyncExtension {
identifier: IExtensionIdentifier;
version?: string;
enabled: boolean;
disabled?: boolean;
}
export interface IGlobalState {
@@ -283,6 +287,9 @@ export interface IUserDataSyncService {
readonly onDidChangeLocal: Event<void>;
readonly lastSyncTime: number | undefined;
readonly onDidChangeLastSyncTime: Event<number>;
pull(): Promise<void>;
sync(): Promise<void>;
stop(): Promise<void>;
@@ -297,7 +304,7 @@ export interface IUserDataSyncService {
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
export interface IUserDataAutoSyncService {
_serviceBrand: any;
readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>;
readonly onError: Event<UserDataSyncError>;
triggerAutoSync(): Promise<void>;
}
@@ -308,19 +315,6 @@ export interface IUserDataSyncUtilService {
resolveFormattingOptions(resource: URI): Promise<FormattingOptions>;
}
export const IUserDataAuthTokenService = createDecorator<IUserDataAuthTokenService>('IUserDataAuthTokenService');
export interface IUserDataAuthTokenService {
_serviceBrand: undefined;
readonly onDidChangeToken: Event<string | undefined>;
readonly onTokenFailed: Event<void>;
getToken(): Promise<string | undefined>;
setToken(accessToken: string | undefined): Promise<void>;
sendTokenFailed(): void;
}
export const IUserDataSyncLogService = createDecorator<IUserDataSyncLogService>('IUserDataSyncLogService');
export interface IUserDataSyncLogService extends ILogService { }
@@ -5,7 +5,7 @@
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event';
import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAuthTokenService, IUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncUtilService, ISettingsSyncService, IUserDataAutoSyncService } 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';
@@ -19,13 +19,14 @@ export class UserDataSyncChannel implements IServerChannel {
case 'onDidChangeStatus': return this.service.onDidChangeStatus;
case 'onDidChangeConflicts': return this.service.onDidChangeConflicts;
case 'onDidChangeLocal': return this.service.onDidChangeLocal;
case 'onDidChangeLastSyncTime': return this.service.onDidChangeLastSyncTime;
}
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources]);
case '_getInitialData': return Promise.resolve([this.service.status, this.service.conflictsSources, this.service.lastSyncTime]);
case 'sync': return this.service.sync();
case 'accept': return this.service.accept(args[0], args[1]);
case 'pull': return this.service.pull();
@@ -90,26 +91,6 @@ export class UserDataAutoSyncChannel implements IServerChannel {
}
}
export class UserDataAuthTokenServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataAuthTokenService) { }
listen(_: unknown, event: string): Event<any> {
switch (event) {
case 'onDidChangeToken': return this.service.onDidChangeToken;
case 'onTokenFailed': return this.service.onTokenFailed;
}
throw new Error(`Event not found: ${event}`);
}
call(context: any, command: string, args?: any): Promise<any> {
switch (command) {
case 'setToken': return this.service.setToken(args);
case 'getToken': return this.service.getToken();
}
throw new Error('Invalid call');
}
}
export class UserDataSycnUtilServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncUtilService) { }
@@ -21,6 +21,7 @@ type SyncErrorClassification = {
};
const SESSION_ID_KEY = 'sync.sessionId';
const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime';
export class UserDataSyncService extends Disposable implements IUserDataSyncService {
@@ -40,6 +41,11 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
private _onDidChangeConflicts: Emitter<SyncSource[]> = this._register(new Emitter<SyncSource[]>());
readonly onDidChangeConflicts: Event<SyncSource[]> = this._onDidChangeConflicts.event;
private _lastSyncTime: number | undefined = undefined;
get lastSyncTime(): number | undefined { return this._lastSyncTime; }
private _onDidChangeLastSyncTime: Emitter<number> = this._register(new Emitter<number>());
readonly onDidChangeLastSyncTime: Event<number> = this._onDidChangeLastSyncTime.event;
private readonly keybindingsSynchroniser: KeybindingsSynchroniser;
private readonly extensionsSynchroniser: ExtensionsSynchroniser;
private readonly globalStateSynchroniser: GlobalStateSynchroniser;
@@ -63,6 +69,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
this._register(Event.any(...this.synchronisers.map(s => Event.map(s.onDidChangeStatus, () => undefined)))(() => this.updateStatus()));
}
this._lastSyncTime = this.storageService.getNumber(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL, undefined);
this.onDidChangeLocal = Event.any(...this.synchronisers.map(s => s.onDidChangeLocal));
}
@@ -156,7 +163,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async accept(source: SyncSource, content: string): Promise<void> {
await this.checkEnablement();
const synchroniser = this.getSynchroniser(source);
return synchroniser.accept(content);
await synchroniser.accept(content);
}
async getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null> {
@@ -189,6 +196,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async resetLocal(): Promise<void> {
await this.checkEnablement();
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL);
for (const synchroniser of this.synchronisers) {
try {
synchroniser.resetLocal();
@@ -227,9 +235,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
}
private setStatus(status: SyncStatus): void {
const oldStatus = this._status;
if (this._status !== status) {
this._status = status;
this._onDidChangeStatus.fire(status);
if (oldStatus !== SyncStatus.Uninitialized && this.status === SyncStatus.Idle) {
this.updateLastSyncTime(new Date().getTime());
}
}
}
@@ -256,6 +268,14 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
return SyncStatus.Idle;
}
private updateLastSyncTime(lastSyncTime: number): void {
if (this._lastSyncTime !== lastSyncTime) {
this._lastSyncTime = lastSyncTime;
this.storageService.store(LAST_SYNC_TIME_KEY, lastSyncTime, StorageScope.GLOBAL);
this._onDidChangeLastSyncTime.fire(lastSyncTime);
}
}
private handleSyncError(e: Error, source: SyncSource): void {
if (e instanceof UserDataSyncStoreError) {
switch (e.code) {
@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, IUserDataAuthTokenService, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, IUserDataSyncStoreService, UserDataSyncErrorCode, IUserDataSyncStore, getUserDataSyncStore, SyncSource, UserDataSyncStoreError, IUserDataSyncLogService, IUserDataManifest } from 'vs/platform/userDataSync/common/userDataSync';
import { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request';
import { joinPath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
@@ -20,7 +21,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
constructor(
@IConfigurationService configurationService: IConfigurationService,
@IRequestService private readonly requestService: IRequestService,
@IUserDataAuthTokenService private readonly authTokenService: IUserDataAuthTokenService,
@IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
) {
super();
@@ -3,10 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { IUserDataSyncService, IUserDataSyncLogService, IUserDataAuthTokenService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserDataSyncService, IUserDataSyncLogService, IUserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSync';
import { Event } from 'vs/base/common/event';
import { IElectronService } from 'vs/platform/electron/node/electron';
import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
@@ -15,7 +16,7 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
@IUserDataSyncService userDataSyncService: IUserDataSyncService,
@IElectronService electronService: IElectronService,
@IUserDataSyncLogService logService: IUserDataSyncLogService,
@IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService,
@IAuthenticationTokenService authTokenService: IAuthenticationTokenService,
) {
super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService);
@@ -11,9 +11,9 @@ 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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, null, null, [], []);
@@ -26,13 +26,13 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, null, null, [], ['a']);
@@ -45,13 +45,13 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, null, null, [], ['A']);
@@ -64,17 +64,17 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, null, null, skippedExtension, []);
@@ -87,16 +87,16 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, null, null, skippedExtension, ['a']);
@@ -109,23 +109,23 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
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 },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
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.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -133,22 +133,22 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
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.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -156,43 +156,66 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
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.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]);
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 () => {
test('merge local and remote extensions when remote is moved forwarded with disabled extension', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
{ identifier: { id: 'd', uuid: 'd' }, disabled: true },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]);
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]);
assert.deepEqual(actual.updated, [{ identifier: { id: 'd', uuid: 'd' }, disabled: true }]);
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' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
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.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
@@ -200,23 +223,23 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
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.added, [{ identifier: { id: 'b', uuid: 'b' } }, { identifier: { id: 'c', uuid: 'c' } }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
@@ -224,23 +247,23 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']);
assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' }, enabled: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'c', uuid: 'c' } }]);
assert.deepEqual(actual.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null);
@@ -248,16 +271,39 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
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 disabled extensions', async () => {
const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, disabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
@@ -270,16 +316,16 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']);
@@ -288,30 +334,30 @@ suite('ExtensionsMerge - No Conflicts', () => {
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, [
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' } },
]);
});
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
@@ -324,23 +370,23 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']);
@@ -353,28 +399,28 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' } }]);
assert.deepEqual(actual.removed, [{ id: 'a', uuid: 'a' }]);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -382,23 +428,23 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']);
@@ -411,30 +457,30 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' }, enabled: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'e', uuid: 'e' } }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -442,25 +488,25 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true },
{ identifier: { id: 'a', uuid: 'a' } },
];
const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
];
const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']);
@@ -473,24 +519,24 @@ suite('ExtensionsMerge - No Conflicts', () => {
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 },
{ identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'A' }, enabled: true },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true },
{ identifier: { id: 'A' } },
{ identifier: { id: 'd', uuid: 'd' } },
];
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 },
{ identifier: { id: 'A', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' } },
];
const actual = merge(localExtensions, remoteExtensions, null, [], []);
assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' }, enabled: true }]);
assert.deepEqual(actual.added, [{ identifier: { id: 'd', uuid: 'd' } }]);
assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected);
@@ -6,7 +6,7 @@
import { IRequestService } from 'vs/platform/request/common/request';
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation';
import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataAuthTokenService, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { IUserData, ResourceKey, IUserDataManifest, ALL_RESOURCE_KEYS, IUserDataSyncLogService, IUserDataSyncStoreService, IUserDataSyncUtilService, IUserDataSyncEnablementService, ISettingsSyncService, IUserDataSyncService } from 'vs/platform/userDataSync/common/userDataSync';
import { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
import { generateUuid } from 'vs/base/common/uuid';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
@@ -33,6 +33,7 @@ import { ConfigurationService } from 'vs/platform/configuration/common/configura
import { Disposable } from 'vs/base/common/lifecycle';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
import { Emitter } from 'vs/base/common/event';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class UserDataSyncClient extends Disposable {
@@ -76,7 +77,7 @@ export class UserDataSyncClient extends Disposable {
this.instantiationService.stub(IConfigurationService, configurationService);
this.instantiationService.stub(IRequestService, this.testServer);
this.instantiationService.stub(IUserDataAuthTokenService, <Partial<IUserDataAuthTokenService>>{
this.instantiationService.stub(IAuthenticationTokenService, <Partial<IAuthenticationTokenService>>{
onDidChangeToken: new Emitter<string | undefined>().event,
async getToken() { return 'token'; }
});
@@ -11,7 +11,7 @@ import { IFileService } from 'vs/platform/files/common/files';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { VSBuffer } from 'vs/base/common/buffer';
suite('UserDataSyncService', () => {
suite.skip('UserDataSyncService', () => { // {{SQL CARBON EDIT}} skip failing tests
const disposableStore = new DisposableStore();
+40 -6
View File
@@ -2897,6 +2897,34 @@ declare module 'vscode' {
constructor(range: Range, newText: string);
}
/**
* Additional data for entries of a workspace edit. Supports to label entries and marks entries
* as needing confirmation by the user. The editor groups edits with equal labels into tree nodes,
* for instance all edits labelled with "Changes in Strings" would be a tree node.
*/
export interface WorkspaceEditEntryMetadata {
/**
* A flag which indicates that user confirmation is needed.
*/
needsConfirmation: boolean;
/**
* A human-readable string which is rendered prominent.
*/
label: string;
/**
* A human-readable string which is rendered less prominent on the same line.
*/
description?: string;
/**
* The icon path or [ThemeIcon](#ThemeIcon) for the edit.
*/
iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
}
/**
* A workspace edit is a collection of textual and files changes for
* multiple resources and documents.
@@ -2916,8 +2944,9 @@ declare module 'vscode' {
* @param uri A resource identifier.
* @param range A range.
* @param newText A string.
* @param metadata Optional metadata for the entry.
*/
replace(uri: Uri, range: Range, newText: string): void;
replace(uri: Uri, range: Range, newText: string, metadata?: WorkspaceEditEntryMetadata): void;
/**
* Insert the given text at the given position.
@@ -2925,16 +2954,18 @@ declare module 'vscode' {
* @param uri A resource identifier.
* @param position A position.
* @param newText A string.
* @param metadata Optional metadata for the entry.
*/
insert(uri: Uri, position: Position, newText: string): void;
insert(uri: Uri, position: Position, newText: string, metadata?: WorkspaceEditEntryMetadata): void;
/**
* Delete the text at the given range.
*
* @param uri A resource identifier.
* @param range A range.
* @param metadata Optional metadata for the entry.
*/
delete(uri: Uri, range: Range): void;
delete(uri: Uri, range: Range, metadata?: WorkspaceEditEntryMetadata): void;
/**
* Check if a text edit for a resource exists.
@@ -2966,15 +2997,17 @@ declare module 'vscode' {
* @param uri Uri of the new file..
* @param options Defines if an existing file should be overwritten or be
* ignored. When overwrite and ignoreIfExists are both set overwrite wins.
* @param metadata Optional metadata for the entry.
*/
createFile(uri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void;
createFile(uri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void;
/**
* Delete a file or folder.
*
* @param uri The uri of the file that is to be deleted.
* @param metadata Optional metadata for the entry.
*/
deleteFile(uri: Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }): void;
deleteFile(uri: Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void;
/**
* Rename a file or folder.
@@ -2983,8 +3016,9 @@ declare module 'vscode' {
* @param newUri The new location.
* @param options Defines if existing files should be overwritten or be
* ignored. When overwrite and ignoreIfExists are both set overwrite wins.
* @param metadata Optional metadata for the entry.
*/
renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }): void;
renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditEntryMetadata): void;
/**
-43
View File
@@ -1408,49 +1408,6 @@ declare module 'vscode' {
//#endregion
//#region https://github.com/microsoft/vscode/issues/77728
/**
* Additional data for entries of a workspace edit. Supports to label entries and marks entries
* as needing confirmation by the user. The editor groups edits with equal labels into tree nodes,
* for instance all edits labelled with "Changes in Strings" would be a tree node.
*/
export interface WorkspaceEditMetadata {
/**
* A flag which indicates that user confirmation is needed.
*/
needsConfirmation: boolean;
/**
* A human-readable string which is rendered prominent.
*/
label: string;
/**
* A human-readable string which is rendered less prominent on the same line.
*/
description?: string;
/**
* The icon path or [ThemeIcon](#ThemeIcon) for the edit.
*/
iconPath?: Uri | { light: Uri; dark: Uri } | ThemeIcon;
}
export interface WorkspaceEdit {
insert(uri: Uri, position: Position, newText: string, metadata?: WorkspaceEditMetadata): void;
delete(uri: Uri, range: Range, metadata?: WorkspaceEditMetadata): void;
replace(uri: Uri, range: Range, newText: string, metadata?: WorkspaceEditMetadata): void;
createFile(uri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditMetadata): void;
deleteFile(uri: Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean }, metadata?: WorkspaceEditMetadata): void;
renameFile(oldUri: Uri, newUri: Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean }, metadata?: WorkspaceEditMetadata): void;
}
//#endregion
//#region Diagnostic links https://github.com/microsoft/vscode/issues/11847
export interface Diagnostic {
@@ -8,16 +8,13 @@ import { FileChangeType, IFileService, FileOperation } from 'vs/platform/files/c
import { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IProgressService, ProgressLocation } from 'vs/platform/progress/common/progress';
import { IProgressService } from 'vs/platform/progress/common/progress';
import { localize } from 'vs/nls';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { URI } from 'vs/base/common/uri';
import { IWaitUntil } from 'vs/base/common/event';
@extHostCustomer
export class MainThreadFileSystemEventService {
@@ -65,43 +62,11 @@ export class MainThreadFileSystemEventService {
// BEFORE file operation
const messages = new Map<FileOperation, string>();
messages.set(FileOperation.CREATE, localize('msg-create', "Running 'File Create' participants..."));
messages.set(FileOperation.DELETE, localize('msg-delete', "Running 'File Delete' participants..."));
messages.set(FileOperation.MOVE, localize('msg-rename', "Running 'File Rename' participants..."));
function participateInFileOperation(e: IWaitUntil, operation: FileOperation, target: URI, source?: URI): void {
const timeout = configService.getValue<number>('files.participants.timeout');
if (timeout <= 0) {
return; // disabled
workingCopyFileService.addFileOperationParticipant({
participate: (target, source, operation, progress, timeout, token) => {
return proxy.$onWillRunFileOperation(operation, target, source, timeout, token);
}
const p = progressService.withProgress({ location: ProgressLocation.Window }, progress => {
progress.report({ message: messages.get(operation) });
return new Promise((resolve, reject) => {
const cts = new CancellationTokenSource();
const timeoutHandle = setTimeout(() => {
logService.trace('CANCELLED file participants because of timeout', timeout, target, operation);
cts.cancel();
reject(new Error('timeout'));
}, timeout);
proxy.$onWillRunFileOperation(operation, target, source, timeout, cts.token)
.then(resolve, reject)
.finally(() => clearTimeout(timeoutHandle));
});
});
e.waitUntil(p);
}
this._listener.add(textFileService.onWillCreateTextFile(e => participateInFileOperation(e, FileOperation.CREATE, e.resource)));
this._listener.add(workingCopyFileService.onBeforeWorkingCopyFileOperation(e => participateInFileOperation(e, e.operation, e.target, e.source)));
});
// AFTER file operation
this._listener.add(textFileService.onDidCreateTextFile(e => proxy.$onDidRunFileOperation(FileOperation.CREATE, e.resource, undefined)));
+12 -2
View File
@@ -174,10 +174,20 @@ export class RemoveFromRecentlyOpenedAPICommand {
}
CommandsRegistry.registerCommand(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute));
export interface OpenIssueReporterArgs {
readonly extensionId: string;
readonly issueTitle?: string;
readonly issueBody?: string;
}
export class OpenIssueReporter {
public static readonly ID = 'vscode.openIssueReporter';
public static execute(executor: ICommandsExecutor, extensionId: string): Promise<void> {
return executor.executeCommand('workbench.action.openIssueReporter', [extensionId]);
public static execute(executor: ICommandsExecutor, args: string | OpenIssueReporterArgs): Promise<void> {
const commandArgs = typeof args === 'string'
? { extensionId: args }
: args;
return executor.executeCommand('workbench.action.openIssueReporter', commandArgs);
}
}
@@ -14,7 +14,7 @@ import * as search from 'vs/workbench/contrib/search/common/search';
import { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures';
import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter } from './apiCommands';
import { ICommandsExecutor, OpenFolderAPICommand, DiffAPICommand, OpenAPICommand, RemoveFromRecentlyOpenedAPICommand, SetEditorLayoutAPICommand, OpenIssueReporter, OpenIssueReporterArgs } from './apiCommands';
import { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IRange } from 'vs/editor/common/core/range';
@@ -364,7 +364,7 @@ export class ExtHostApiCommands {
this._register(OpenIssueReporter.ID, adjustHandler(OpenIssueReporter.execute), {
description: 'Opens the issue reporter with the provided extension id as the selected source',
args: [
{ name: 'extensionId', description: 'extensionId to report an issue on', constraint: (value: any) => typeof value === 'string' }
{ name: 'extensionId', description: 'extensionId to report an issue on', constraint: (value: unknown) => typeof value === 'string' || (typeof value === 'object' && typeof (value as OpenIssueReporterArgs).extensionId === 'string') }
]
});
}
@@ -26,7 +26,7 @@ import { Schemas } from 'vs/base/common/network';
import { VSBuffer } from 'vs/base/common/buffer';
import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes';
import { ResolvedAuthority, ResolvedOptions } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { ResolvedAuthority, ResolvedOptions, RemoteAuthorityResolverErrorCode } from 'vs/platform/remote/common/remoteAuthorityResolver';
import { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
@@ -641,7 +641,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const resolver = this._resolvers[authorityPrefix];
if (!resolver) {
throw new Error(`No remote extension installed to resolve ${authorityPrefix}.`);
return {
type: 'error',
error: {
code: RemoteAuthorityResolverErrorCode.NoResolverFound,
message: `No remote extension installed to resolve ${authorityPrefix}.`,
detail: undefined
}
};
}
try {
+8 -8
View File
@@ -576,14 +576,14 @@ export interface IFileOperation {
from?: URI;
to?: URI;
options?: IFileOperationOptions;
metadata?: vscode.WorkspaceEditMetadata;
metadata?: vscode.WorkspaceEditEntryMetadata;
}
export interface IFileTextEdit {
_type: 2;
uri: URI;
edit: TextEdit;
metadata?: vscode.WorkspaceEditMetadata;
metadata?: vscode.WorkspaceEditEntryMetadata;
}
@es5ClassCompat
@@ -591,27 +591,27 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
private _edits = new Array<IFileOperation | IFileTextEdit>();
renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void {
renameFile(from: vscode.Uri, to: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: 1, from, to, options, metadata });
}
createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void {
createFile(uri: vscode.Uri, options?: { overwrite?: boolean, ignoreIfExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: 1, from: undefined, to: uri, options, metadata });
}
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }, metadata?: vscode.WorkspaceEditMetadata): void {
deleteFile(uri: vscode.Uri, options?: { recursive?: boolean, ignoreIfNotExists?: boolean; }, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: 1, from: uri, to: undefined, options, metadata });
}
replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditMetadata): void {
replace(uri: URI, range: Range, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this._edits.push({ _type: 2, uri, edit: new TextEdit(range, newText), metadata });
}
insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditMetadata): void {
insert(resource: URI, position: Position, newText: string, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this.replace(resource, new Range(position, position), newText, metadata);
}
delete(resource: URI, range: Range, metadata?: vscode.WorkspaceEditMetadata): void {
delete(resource: URI, range: Range, metadata?: vscode.WorkspaceEditEntryMetadata): void {
this.replace(resource, range, '', metadata);
}
@@ -9,7 +9,7 @@ import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform';
import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/common/actions';
import { IWorkbenchActionRegistry, Extensions } from 'vs/workbench/common/actions';
import { IWorkbenchActionRegistry, Extensions as WorkbenchExtensions } from 'vs/workbench/common/actions';
import { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService';
@@ -24,8 +24,9 @@ import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleConte
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions } from 'vs/workbench/common/views';
const registry = Registry.as<IWorkbenchActionRegistry>(Extensions.WorkbenchActions);
const registry = Registry.as<IWorkbenchActionRegistry>(WorkbenchExtensions.WorkbenchActions);
const viewCategory = nls.localize('view', "View");
// --- Close Side Bar
@@ -482,6 +483,42 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
order: 0
});
// --- Reset View Positions
export class ResetViewLocationsAction extends Action {
static readonly ID = 'workbench.action.resetViewLocations';
static readonly LABEL = nls.localize('resetViewLocations', "Reset View Locations");
constructor(
id: string,
label: string,
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService
) {
super(id, label);
}
run(): Promise<void> {
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
viewContainerRegistry.all.forEach(viewContainer => {
const viewDescriptors = this.viewDescriptorService.getViewDescriptors(viewContainer);
viewDescriptors.allViewDescriptors.forEach(viewDescriptor => {
const defaultContainer = this.viewDescriptorService.getDefaultContainer(viewDescriptor.id);
const currentContainer = this.viewDescriptorService.getViewContainer(viewDescriptor.id);
if (defaultContainer && currentContainer !== defaultContainer) {
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer);
}
});
});
return Promise.resolve();
}
}
registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetViewLocationsAction, ResetViewLocationsAction.ID, ResetViewLocationsAction.LABEL), 'View: Reset View Locations', viewCategory);
// --- Resize View
export abstract class BaseResizeViewAction extends Action {
+1
View File
@@ -1180,6 +1180,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
} else {
this.setEditorHidden(false);
this.workbenchGrid.resizeView(this.panelPartView, { width: this.state.panel.position === Position.BOTTOM ? size.width : this.state.panel.lastNonMaximizedWidth, height: this.state.panel.position === Position.BOTTOM ? this.state.panel.lastNonMaximizedHeight : size.height });
this.editorGroupService.activeGroup.focus();
}
}
@@ -19,14 +19,14 @@ import { ToggleActivityBarVisibilityAction, ToggleMenuBarAction } from 'vs/workb
import { IThemeService, ITheme } from 'vs/platform/theme/common/themeService';
import { ACTIVITY_BAR_BACKGROUND, ACTIVITY_BAR_BORDER, ACTIVITY_BAR_FOREGROUND, ACTIVITY_BAR_ACTIVE_BORDER, ACTIVITY_BAR_BADGE_BACKGROUND, ACTIVITY_BAR_BADGE_FOREGROUND, ACTIVITY_BAR_DRAG_AND_DROP_BACKGROUND, ACTIVITY_BAR_INACTIVE_FOREGROUND, ACTIVITY_BAR_ACTIVE_BACKGROUND } from 'vs/workbench/common/theme';
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar, ICompositeBarItem } from 'vs/workbench/browser/parts/compositeBar';
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
import { Dimension, addClass, removeNode } from 'vs/base/browser/dom';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet';
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection } from 'vs/workbench/common/views';
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainer, TEST_VIEW_CONTAINER_ID, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views';
import { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IViewlet } from 'vs/workbench/common/viewlet';
import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
@@ -128,6 +128,11 @@ export class ActivitybarPart extends Part implements IActivityBarService {
getContextMenuActionsForComposite: () => [],
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
hidePart: () => this.layoutService.setSideBarHidden(true),
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Sidebar,
(id: string, focus?: boolean) => this.viewletService.openViewlet(id, focus),
(from: string, to: string) => this.compositeBar.move(from, to),
() => this.getPinnedViewletIds()
),
compositeSize: 50,
colors: (theme: ITheme) => this.getActivitybarItemColors(theme),
overflowActionSize: ActivitybarPart.ACTION_HEIGHT
+154 -1
View File
@@ -20,6 +20,10 @@ import { isUndefinedOrNull } from 'vs/base/common/types';
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { ITheme } from 'vs/platform/theme/common/themeService';
import { Emitter } from 'vs/base/common/event';
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { Registry } from 'vs/platform/registry/common/platform';
import { IViewContainersRegistry, Extensions as ViewContainerExtensions, ViewContainerLocation, IViewDescriptorService } from 'vs/workbench/common/views';
import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd';
export interface ICompositeBarItem {
id: string;
@@ -29,12 +33,120 @@ export interface ICompositeBarItem {
visible: boolean;
}
export class CompositeDragAndDrop implements ICompositeDragAndDrop {
constructor(
private viewDescriptorService: IViewDescriptorService,
private targetContainerLocation: ViewContainerLocation,
private openComposite: (id: string, focus?: boolean) => void,
private moveComposite: (from: string, to: string) => void,
private getVisibleCompositeIds: () => string[]
) { }
drop(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): void {
const dragData = data.getData();
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
if (dragData.type === 'composite') {
const currentContainer = viewContainerRegistry.get(dragData.id)!;
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
if (targetCompositeId) {
if (currentLocation !== this.targetContainerLocation && this.targetContainerLocation !== ViewContainerLocation.Panel) {
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
if (destinationContainer) {
this.viewDescriptorService.moveViewsToContainer(this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.filter(vd => vd.canMoveView), destinationContainer);
this.openComposite(targetCompositeId, true);
}
} else {
this.moveComposite(dragData.id, targetCompositeId);
}
}
} else {
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
if (viewDescriptor && viewDescriptor.canMoveView) {
if (targetCompositeId) {
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
if (destinationContainer) {
if (this.targetContainerLocation === ViewContainerLocation.Sidebar) {
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], destinationContainer);
this.openComposite(targetCompositeId, true);
} else {
this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation);
this.moveComposite(this.viewDescriptorService.getViewContainer(viewDescriptor.id)!.id, targetCompositeId);
}
}
} else {
this.viewDescriptorService.moveViewToLocation(viewDescriptor, this.targetContainerLocation);
const newCompositeId = this.viewDescriptorService.getViewContainer(dragData.id)!.id;
const visibleItems = this.getVisibleCompositeIds();
const targetId = visibleItems.length ? visibleItems[visibleItems.length - 1] : undefined;
if (targetId && targetId !== newCompositeId) {
this.moveComposite(newCompositeId, targetId);
}
this.openComposite(newCompositeId, true);
}
}
}
}
onDragOver(data: CompositeDragAndDropData, targetCompositeId: string | undefined, originalEvent: DragEvent): boolean {
const dragData = data.getData();
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
if (dragData.type === 'composite') {
// Dragging a composite
const currentContainer = viewContainerRegistry.get(dragData.id)!;
const currentLocation = viewContainerRegistry.getViewContainerLocation(currentContainer);
// ... to the same location
if (currentLocation === this.targetContainerLocation) {
return true;
}
// ... across view containers but without a destination composite
if (!targetCompositeId) {
return false;
}
// ... from panel to the sidebar
if (this.targetContainerLocation === ViewContainerLocation.Sidebar) {
const destinationContainer = viewContainerRegistry.get(targetCompositeId);
return !!destinationContainer &&
this.viewDescriptorService.getViewDescriptors(currentContainer)!.allViewDescriptors.some(vd => vd.canMoveView);
}
// ... from sidebar to the panel
else {
return false;
}
} else {
// Dragging an individual view
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
// ... that cannot move
if (!viewDescriptor || !viewDescriptor.canMoveView) {
return false;
}
// ... to create a view container
if (!targetCompositeId) {
return this.targetContainerLocation === ViewContainerLocation.Panel;
}
// ... into a destination
return true;
}
return false;
}
}
export interface ICompositeBarOptions {
readonly icon: boolean;
readonly orientation: ActionsOrientation;
readonly colors: (theme: ITheme) => ICompositeBarColors;
readonly compositeSize: number;
readonly overflowActionSize: number;
readonly dndHandler: ICompositeDragAndDrop;
getActivityAction: (compositeId: string) => ActivityAction;
getCompositePinnedAction: (compositeId: string) => Action;
@@ -58,7 +170,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
private visibleComposites: string[];
private compositeSizeInBar: Map<string, number>;
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier>;
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event;
@@ -107,6 +219,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
() => this.getContextMenuActions() as Action[],
this.options.colors,
this.options.icon,
this.options.dndHandler,
this
);
},
@@ -134,6 +247,46 @@ export class CompositeBar extends Widget implements ICompositeBar {
}
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
this.compositeTransfer.clearData(DraggedViewIdentifier.prototype);
this.options.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), undefined, e);
}
}
}));
this._register(addDisposableListener(parent, EventType.DRAG_OVER, (e: DragEvent) => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
EventHelper.stop(e, true);
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
// Check if drop is allowed
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), undefined, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
EventHelper.stop(e, true);
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
// Check if drop is allowed
if (e.dataTransfer && !this.options.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), undefined, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
}));
return actionBarDiv;
@@ -20,6 +20,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Emitter } from 'vs/base/common/event';
import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color';
import { DraggedViewIdentifier } from 'vs/workbench/browser/parts/views/viewPaneContainer';
import { ICompositeDragAndDrop, CompositeDragAndDropData } from 'vs/base/parts/composite/browser/compositeDnd';
export interface ICompositeActivity {
badge: IBadge;
@@ -458,7 +460,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
private static manageExtensionAction: ManageExtensionAction;
private compositeActivity: IActivity | undefined;
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier>;
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
constructor(
private compositeActivityAction: ActivityAction,
@@ -467,6 +469,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
private contextMenuActionsProvider: () => ReadonlyArray<Action>,
colors: (theme: ITheme) => ICompositeBarColors,
icon: boolean,
private dndHandler: ICompositeDragAndDrop,
private compositeBar: ICompositeBar,
@IContextMenuService private readonly contextMenuService: IContextMenuService,
@IKeybindingService private readonly keybindingService: IKeybindingService,
@@ -475,7 +478,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
) {
super(compositeActivityAction, { draggable: true, colors, icon }, themeService);
this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier>();
this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier | DraggedViewIdentifier>();
if (!CompositeActionViewItem.manageExtensionAction) {
CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(ManageExtensionAction);
@@ -546,6 +549,31 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
}
},
onDragOver: e => {
dom.EventHelper.stop(e, true);
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedCompositeIdentifier.prototype);
if (Array.isArray(data)) {
const draggedCompositeId = data[0].id;
if (draggedCompositeId !== this.activity.id) {
if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
if (e.dataTransfer && !this.dndHandler.onDragOver(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e)) {
e.dataTransfer.dropEffect = 'none';
}
}
}
},
onDragLeave: e => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
this.updateFromDragging(container, false);
@@ -571,16 +599,27 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.updateFromDragging(container, false);
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype);
this.compositeBar.move(draggedCompositeId, this.activity.id);
this.dndHandler.drop(new CompositeDragAndDropData('composite', draggedCompositeId), this.activity.id, e);
}
}
}
if (this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) {
const data = this.compositeTransfer.getData(DraggedViewIdentifier.prototype);
if (Array.isArray(data)) {
const draggedViewId = data[0].id;
this.dndHandler.drop(new CompositeDragAndDropData('view', draggedViewId), this.activity.id, e);
}
}
}
}));
// Activate on drag over to reveal targets
[this.badge, this.label].forEach(b => this._register(new DelayedDragHandler(b, () => {
if (!this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) && !this.getAction().checked) {
if (!(this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype) ||
this.compositeTransfer.hasData(DraggedViewIdentifier.prototype)) &&
!this.getAction().checked) {
this.getAction().run();
}
})));
@@ -22,7 +22,7 @@ import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, Togg
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService';
import { PANEL_BACKGROUND, PANEL_BORDER, PANEL_ACTIVE_TITLE_FOREGROUND, PANEL_INACTIVE_TITLE_FOREGROUND, PANEL_ACTIVE_TITLE_BORDER, PANEL_DRAG_AND_DROP_BACKGROUND, PANEL_INPUT_BORDER } from 'vs/workbench/common/theme';
import { activeContrastBorder, focusBorder, contrastBorder, editorBackground, badgeBackground, badgeForeground } from 'vs/platform/theme/common/colorRegistry';
import { CompositeBar, ICompositeBarItem } from 'vs/workbench/browser/parts/compositeBar';
import { CompositeBar, ICompositeBarItem, CompositeDragAndDrop } from 'vs/workbench/browser/parts/compositeBar';
import { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { INotificationService } from 'vs/platform/notification/common/notification';
@@ -33,7 +33,7 @@ import { IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/con
import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection } from 'vs/workbench/common/views';
import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewDescriptorService, IViewDescriptorCollection, ViewContainerLocation } from 'vs/workbench/common/views';
import { MenuId } from 'vs/platform/actions/common/actions';
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
@@ -142,6 +142,11 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[],
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
hidePart: () => this.layoutService.setPanelHidden(true),
dndHandler: new CompositeDragAndDrop(this.viewDescriptorService, ViewContainerLocation.Panel,
(id: string, focus?: boolean) => this.openPanel(id, focus),
(from: string, to: string) => this.compositeBar.move(from, to),
() => this.getPinnedPanels().map(p => p.id)
),
compositeSize: 0,
overflowActionSize: 44,
colors: (theme: ITheme) => ({
@@ -397,7 +402,17 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
getPanels(): readonly PanelDescriptor[] {
return this.panelRegistry.getPanels()
.sort((v1, v2) => typeof v1.order === 'number' && typeof v2.order === 'number' ? v1.order - v2.order : NaN);
.sort((v1, v2) => {
if (typeof v1.order !== 'number') {
return 1;
}
if (typeof v2.order !== 'number') {
return -1;
}
return v1.order - v2.order;
});
}
getPinnedPanels(): readonly PanelDescriptor[] {
@@ -42,6 +42,7 @@ import { parseLinkedText } from 'vs/base/common/linkedText';
import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Button } from 'vs/base/browser/ui/button/button';
import { Link } from 'vs/platform/opener/browser/link';
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier;
@@ -57,6 +58,15 @@ export interface IViewPaneOptions extends IPaneOptions {
titleMenuId?: MenuId;
}
export class DraggedViewIdentifier {
constructor(private _viewId: string) { }
get id(): string {
return this._viewId;
}
}
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
interface IItem {
@@ -444,6 +454,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
private paneItems: IViewPaneItem[] = [];
private paneview?: PaneView;
private static viewTransfer = LocalSelectionTransfer.getInstance<DraggedViewIdentifier>();
private visible: boolean = false;
private areExtensionsReady: boolean = false;
@@ -874,6 +886,22 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.paneItems.splice(index, 0, paneItem);
assertIsDefined(this.paneview).addPane(pane, size, index);
this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_START, (e: DragEvent) => {
if (e.dataTransfer) {
e.dataTransfer.effectAllowed = 'move';
}
// Register as dragged to local transfer
ViewPaneContainer.viewTransfer.setData([new DraggedViewIdentifier(pane.id)], DraggedViewIdentifier.prototype);
}));
this._register(addDisposableListener(pane.draggableElement, EventType.DRAG_END, (e: DragEvent) => {
if (ViewPaneContainer.viewTransfer.hasData(DraggedViewIdentifier.prototype)) {
ViewPaneContainer.viewTransfer.clearData(DraggedViewIdentifier.prototype);
}
}));
}
removePanes(panes: ViewPane[]): void {
@@ -952,7 +980,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
}
if (!this.areExtensionsReady) {
if (this.visibleViewsCountFromCache === undefined) {
return false;
return true;
}
// Check in cache so that view do not jump. See #29609
return this.visibleViewsCountFromCache === 1;
@@ -591,7 +591,7 @@ export class ViewsService extends Disposable implements IViewsService {
}
run(accessor: ServicesAccessor): any {
accessor.get(IViewDescriptorService).moveViewToLocation(viewDescriptor, newLocation);
accessor.get(IViewsService).openView(viewDescriptor.id);
accessor.get(IViewsService).openView(viewDescriptor.id, true);
}
}));
+27 -1
View File
@@ -22,7 +22,7 @@ import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/
import { IPathData } from 'vs/platform/windows/common/windows';
import { coalesce, firstOrDefault } from 'vs/base/common/arrays';
import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorService, IResourceEditor } from 'vs/workbench/services/editor/common/editorService';
import { isEqual, dirname } from 'vs/base/common/resources';
import { IPanel } from 'vs/workbench/common/panel';
import { IRange } from 'vs/editor/common/core/range';
@@ -345,6 +345,11 @@ export interface IRevertOptions {
readonly soft?: boolean;
}
export interface IMoveResult {
editor: IEditorInput | IResourceEditor;
options?: IEditorOptions;
}
export interface IEditorInput extends IDisposable {
/**
@@ -446,6 +451,16 @@ export interface IEditorInput extends IDisposable {
*/
revert(group: GroupIdentifier, options?: IRevertOptions): Promise<boolean>;
/**
* Called to determine how to handle a resource that is moved that matches
* the editors resource (or is a child of).
*
* Implementors are free to not implement this method to signal no intent
* to participate. If an editor is returned though, it will replace the
* current one with that editor and optional options.
*/
move(group: GroupIdentifier, target: URI): IMoveResult | undefined;
/**
* Returns if the other object matches this input.
*/
@@ -546,6 +561,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
return true;
}
move(group: GroupIdentifier, target: URI): IMoveResult | undefined {
return undefined;
}
/**
* Subclasses can set this to false if it does not make sense to split the editor input.
*/
@@ -780,6 +799,11 @@ export interface IFileEditorInput extends IEditorInput, IEncodingSupport, IModeS
* Forces this file input to open as binary instead of text.
*/
setForceOpenAsBinary(): void;
/**
* Figure out if the input has been resolved or not.
*/
isResolved(): boolean;
}
/**
@@ -1209,6 +1233,8 @@ export class TextEditorOptions extends EditorOptions implements ITextEditorOptio
if (this.selectionRevealType === TextEditorSelectionRevealType.NearTop) {
editor.revealRangeNearTop(range, scrollType);
} else if (this.selectionRevealType === TextEditorSelectionRevealType.NearTopIfOutsideViewport) {
editor.revealRangeNearTopIfOutsideViewport(range, scrollType);
} else if (this.selectionRevealType === TextEditorSelectionRevealType.CenterIfOutsideViewport) {
editor.revealRangeInCenterIfOutsideViewport(range, scrollType);
} else {

Some files were not shown because too many files have changed in this diff Show More