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 yarn gulp minify-vscode-reh-web
displayName: Compile displayName: Compile
condition: and(succeeded(), ne(variables['CacheExists-Compilation'], 'true')) 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: | - script: |
set -e set -e
+13
View File
@@ -74,6 +74,7 @@ function compileTask(src, out, build) {
if (src === 'src') { if (src === 'src') {
generator.execute(); generator.execute();
} }
// generateGitHubAuthConfig();
return srcPipe return srcPipe
.pipe(generator.stream) .pipe(generator.stream)
.pipe(compile()) .pipe(compile())
@@ -96,6 +97,18 @@ function watchTask(out, build) {
} }
exports.watchTask = watchTask; exports.watchTask = watchTask;
const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); 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 { class MonacoGenerator {
constructor(isWatch) { constructor(isWatch) {
this._executeSoonTimer = null; this._executeSoonTimer = null;
+15
View File
@@ -88,6 +88,8 @@ export function compileTask(src: string, out: string, build: boolean): () => Nod
generator.execute(); generator.execute();
} }
// generateGitHubAuthConfig();
return srcPipe return srcPipe
.pipe(generator.stream) .pipe(generator.stream)
.pipe(compile()) .pipe(compile())
@@ -115,6 +117,19 @@ export function watchTask(out: string, build: boolean): () => NodeJS.ReadWriteSt
const REPO_SRC_FOLDER = path.join(__dirname, '../../src'); 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 { class MonacoGenerator {
private readonly _isWatch: boolean; private readonly _isWatch: boolean;
public readonly stream: NodeJS.ReadWriteStream; public readonly stream: NodeJS.ReadWriteStream;
+4
View File
@@ -302,6 +302,10 @@
"name": "vs/workbench/services/textMate", "name": "vs/workbench/services/textMate",
"project": "vscode-workbench" "project": "vscode-workbench"
}, },
{
"name": "vs/workbench/services/workingCopy",
"project": "vscode-workbench"
},
{ {
"name": "vs/workbench/services/workspaces", "name": "vs/workbench/services/workspaces",
"project": "vscode-workbench" "project": "vscode-workbench"
+1 -1
View File
@@ -232,7 +232,7 @@ function onUnexpectedError(error, enableDeveloperTools) {
console.error('[uncaught exception]: ' + error); console.error('[uncaught exception]: ' + error);
if (error.stack) { if (error && error.stack) {
console.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>) { public init(grid: Slick.Grid<T>) {
this.grid = grid; 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() { public destroy() {
@@ -59,9 +59,9 @@ export class CellRangeSelector<T> implements ICellRangeSelector<T> {
this.canvas = this.grid.getCanvasNode(); this.canvas = this.grid.getCanvasNode();
this.handler this.handler
.subscribe(this.grid.onDragInit, e => this.handleDragInit(e)) .subscribe(this.grid.onDragInit, e => this.handleDragInit(e))
.subscribe(this.grid.onDragStart, (e: MouseEvent, dd) => this.handleDragStart(e, dd)) .subscribe(this.grid.onDragStart, (e: DOMEvent, dd) => this.handleDragStart(e as MouseEvent, dd))
.subscribe(this.grid.onDrag, (e: MouseEvent, dd) => this.handleDrag(e, dd)) .subscribe(this.grid.onDrag, (e: DOMEvent, dd) => this.handleDrag(e as MouseEvent, dd))
.subscribe(this.grid.onDragEnd, (e: MouseEvent, dd) => this.handleDragEnd(e, dd)); .subscribe(this.grid.onDragEnd, (e: DOMEvent, dd) => this.handleDragEnd(e as MouseEvent, dd));
} }
public destroy() { public destroy() {
@@ -36,10 +36,10 @@ export class CellSelectionModel<T> implements Slick.SelectionModel<T, Array<Slic
public init(grid: Slick.Grid<T>) { public init(grid: Slick.Grid<T>) {
this.grid = grid; this.grid = grid;
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(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: KeyboardEvent) => this.handleKeyDown(e)); this._handler.subscribe(this.grid.onKeyDown, (e: DOMEvent) => this.handleKeyDown(e as KeyboardEvent));
this._handler.subscribe(this.grid.onClick, (e: MouseEvent, args: Slick.OnClickEventArgs<T>) => this.handleIndividualCellSelection(e, args)); 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: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, 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.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.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)); 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._grid = grid;
this._handler this._handler
.subscribe(this._grid.onSelectedRowsChanged, (e: Event, args: Slick.OnSelectedRowsChangedEventArgs<T>) => this.handleSelectedRowsChanged(e, args)) .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.onClick, (e: DOMEvent, args: Slick.OnClickEventArgs<T>) => this.handleClick(e as MouseEvent, args))
.subscribe(this._grid.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args)) .subscribe(this._grid.onHeaderClick, (e: DOMEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e as MouseEvent, args))
.subscribe(this._grid.onKeyDown, (e: KeyboardEvent, args: Slick.OnKeyDownEventArgs<T>) => this.handleKeyDown(e, args)); .subscribe(this._grid.onKeyDown, (e: DOMEvent, args: Slick.OnKeyDownEventArgs<T>) => this.handleKeyDown(e as KeyboardEvent, args));
} }
public destroy(): void { public destroy(): void {
@@ -20,7 +20,7 @@ export class CopyKeybind<T> implements Slick.Plugin<T> {
public init(grid: Slick.Grid<T>) { public init(grid: Slick.Grid<T>) {
this.grid = grid; 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() { public destroy() {
@@ -36,9 +36,9 @@ export class HeaderFilter<T extends Slick.SlickData> {
this.grid = grid; this.grid = grid;
this.handler.subscribe(this.grid.onHeaderCellRendered, (e: Event, args: Slick.OnHeaderCellRenderedEventArgs<T>) => this.handleHeaderCellRendered(e, args)) 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.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.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.grid.setColumns(this.grid.getColumns());
this.disposableStore.add(addDisposableListener(document.body, 'mousedown', e => this.handleBodyMouseDown(e))); 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._grid.getOptions().minRowBuffer = this._options.panelRows + 3;
this._handler 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.onSort, () => this.handleSort())
.subscribe(this._grid.onScroll, () => this.handleScroll()); .subscribe(this._grid.onScroll, () => this.handleScroll());
@@ -18,8 +18,8 @@ export class RowNumberColumn<T> implements Slick.Plugin<T> {
public init(grid: Slick.Grid<T>) { public init(grid: Slick.Grid<T>) {
this.grid = grid; this.grid = grid;
this.handler 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.onHeaderClick, (e: MouseEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e, args)); .subscribe(this.grid.onHeaderClick, (e: DOMEvent, args: Slick.OnHeaderClickEventArgs<T>) => this.handleHeaderClick(e as MouseEvent, args));
} }
public destroy() { public destroy() {
@@ -26,8 +26,8 @@ export class RowSelectionModel<T extends Slick.SlickData> implements Slick.Selec
this._grid = grid; this._grid = grid;
this._handler this._handler
.subscribe(this._grid.onActiveCellChanged, (e: Event, data: Slick.OnActiveCellChangedEventArgs<T>) => this.handleActiveCellChange(e, data)) .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.onKeyDown, (e: DOMEvent) => this.handleKeyDown(e as KeyboardEvent))
.subscribe(this._grid.onClick, (e: MouseEvent) => this.handleClick(e)); .subscribe(this._grid.onClick, (e: DOMEvent) => this.handleClick(e as MouseEvent));
} }
private rangesToRows(ranges: Slick.Range[]): number[] { 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>) { private mapMouseEvent(slickEvent: Slick.Event<any>, emitter: Emitter<ITableMouseEvent>) {
slickEvent.subscribe((e: JQuery.Event) => { slickEvent.subscribe((e: Slick.EventData) => {
const originalEvent = e.originalEvent; const originalEvent = (e as JQuery.Event).originalEvent;
const cell = this._grid.getCellFromEvent(originalEvent); const cell = this._grid.getCellFromEvent(originalEvent);
const anchor = originalEvent instanceof MouseEvent ? { x: originalEvent.x, y: originalEvent.y } : originalEvent.srcElement as HTMLElement; const anchor = originalEvent instanceof MouseEvent ? { x: originalEvent.x, y: originalEvent.y } : originalEvent.srcElement as HTMLElement;
emitter.fire({ anchor, cell }); emitter.fire({ anchor, cell });
+3 -3
View File
@@ -8,8 +8,8 @@ import 'vs/css!./media/icons';
import { ActionBar } from './actionbar'; import { ActionBar } from './actionbar';
import { Action, IActionRunner, IAction } from 'vs/base/common/actions'; import { IActionRunner, IAction } from 'vs/base/common/actions';
import { ActionsOrientation } from 'vs/base/browser/ui/actionbar/actionbar'; import { ActionsOrientation, IActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
import { IToolBarOptions } from 'vs/base/browser/ui/toolbar/toolbar'; import { IToolBarOptions } from 'vs/base/browser/ui/toolbar/toolbar';
/** /**
@@ -43,7 +43,7 @@ export class Taskbar {
this.actionBar = new ActionBar(element, { this.actionBar = new ActionBar(element, {
orientation: options.orientation, orientation: options.orientation,
ariaLabel: options.ariaLabel, ariaLabel: options.ariaLabel,
actionViewItemProvider: (action: Action) => { actionViewItemProvider: (action: IAction): IActionViewItem | undefined => {
return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined; return options.actionViewItemProvider ? options.actionViewItemProvider(action) : undefined;
} }
}); });
@@ -5,7 +5,7 @@
import * as assert from 'assert'; import * as assert from 'assert';
import * as azdata from 'azdata'; 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 { ConnectionConfig, ISaveGroupResult } from 'sql/platform/connection/common/connectionConfig';
import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile'; import { ConnectionProfile } from 'sql/platform/connection/common/connectionProfile';
import { ConnectionProfileGroup, IConnectionProfileGroup } from 'sql/platform/connection/common/connectionProfileGroup'; 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'; import { find } from 'vs/base/common/arrays';
suite('ConnectionConfig', () => { suite('ConnectionConfig', () => {
let capabilitiesService: TypeMoq.Mock<ICapabilitiesService>; let capabilitiesService: TypeMoq.Mock<TestCapabilitiesService>;
let msSQLCapabilities: ProviderFeatures; let msSQLCapabilities: ProviderFeatures;
let capabilities: ProviderFeatures[]; let capabilities: ProviderFeatures[];
let onCapabilitiesRegistered = new Emitter<ProviderFeatures>(); let onCapabilitiesRegistered = new Emitter<ProviderFeatures>();
+1 -6
View File
@@ -2,15 +2,10 @@
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"compilerOptions": { "compilerOptions": {
"noEmit": true, "noEmit": true,
"noImplicitAny": true,
"experimentalDecorators": true, "experimentalDecorators": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noUnusedLocals": true, "noUnusedLocals": true,
"noImplicitThis": true, "strict": true,
"alwaysStrict": true,
"strictBindCallApply": true,
"strictNullChecks": true,
"strictPropertyInitialization": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"skipLibCheck": true "skipLibCheck": true
}, },
+3 -5
View File
@@ -25,7 +25,7 @@ export interface MarkdownRenderOptions extends FormattedTextRenderOptions {
/** /**
* Create html nodes for the given content element. * 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 element = createElement(options);
const _uriMassage = function (part: string): string { const _uriMassage = function (part: string): string {
@@ -183,10 +183,8 @@ export function renderMarkdown(markdown: IMarkdownString, options: MarkdownRende
})); }));
} }
const markedOptions: marked.MarkedOptions = { markedOptions.sanitize = true;
sanitize: true, markedOptions.renderer = renderer;
renderer
};
const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource]; const allowedSchemes = [Schemas.http, Schemas.https, Schemas.mailto, Schemas.data, Schemas.file, Schemas.vscodeRemote, Schemas.vscodeRemoteResource];
if (markdown.isTrusted) { 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 contentHeight(): number { return this.rangeMap.size; }
get onDidScroll(): Event<ScrollEvent> { return this.scrollableElement.onScroll; } get onDidScroll(): Event<ScrollEvent> { return this.scrollableElement.onScroll; }
get onWillScroll(): Event<ScrollEvent> { return this.scrollableElement.onWillScroll; }
constructor( constructor(
container: HTMLElement, container: HTMLElement,
@@ -167,6 +167,9 @@ export abstract class AbstractScrollableElement extends Widget {
private readonly _onScroll = this._register(new Emitter<ScrollEvent>()); private readonly _onScroll = this._register(new Emitter<ScrollEvent>());
public readonly onScroll: Event<ScrollEvent> = this._onScroll.event; 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) { protected constructor(element: HTMLElement, options: ScrollableElementCreationOptions, scrollable: Scrollable) {
super(); super();
element.style.overflow = 'hidden'; element.style.overflow = 'hidden';
@@ -174,6 +177,7 @@ export abstract class AbstractScrollableElement extends Widget {
this._scrollable = scrollable; this._scrollable = scrollable;
this._register(this._scrollable.onScroll((e) => { this._register(this._scrollable.onScroll((e) => {
this._onWillScroll.fire(e);
this._onDidScroll(e); this._onDidScroll(e);
this._onScroll.fire(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)))]); 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> { export function asPromise<T>(callback: () => T | Thenable<T>): Promise<T> {
return new Promise<T>((resolve, reject) => { return new Promise<T>((resolve, reject) => {
const item = callback(); const item = callback();
+2 -2
View File
@@ -13,7 +13,7 @@ const month = day * 30;
const year = day * 365; const year = day * 365;
// TODO[ECA]: Localize strings // TODO[ECA]: Localize strings
export function fromNow(date: number | Date) { export function fromNow(date: number | Date, appendAgoLabel?: boolean): string {
if (typeof date !== 'number') { if (typeof date !== 'number') {
date = date.getTime(); date = date.getTime();
} }
@@ -48,7 +48,7 @@ export function fromNow(date: number | Date) {
unit = 'yr'; 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 * as async from 'vs/base/common/async';
import { isPromiseCanceledError } from 'vs/base/common/errors'; import { isPromiseCanceledError } from 'vs/base/common/errors';
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { CancellationTokenSource } from 'vs/base/common/cancellation';
suite('Async', () => { suite('Async', () => {
@@ -646,4 +647,45 @@ suite('Async', () => {
assert.ok(pendingCancelled); 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); 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>) => { ipcRenderer.on('vscode:issuePerformanceInfoResponse', (_: unknown, info: Partial<IssueReporterData>) => {
this.logService.trace('issueReporter: Received performance data'); this.logService.trace('issueReporter: Received performance data');
this.issueReporterModel.update(info); this.issueReporterModel.update(info);
@@ -1176,8 +1193,8 @@ export class IssueReporter extends Disposable {
} }
} }
private getElementById(elementId: string): HTMLElement | undefined { private getElementById<T extends HTMLElement = HTMLElement>(elementId: string): T | undefined {
const element = document.getElementById(elementId); const element = document.getElementById(elementId) as T | undefined;
if (element) { if (element) {
return element; return element;
} else { } else {
@@ -49,10 +49,10 @@ import { IFileService } from 'vs/platform/files/common/files';
import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider'; import { DiskFileSystemProvider } from 'vs/platform/files/electron-browser/diskFileSystemProvider';
import { Schemas } from 'vs/base/common/network'; import { Schemas } from 'vs/base/common/network';
import { IProductService } from 'vs/platform/product/common/productService'; 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 { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
import { UserDataSyncStoreService } from 'vs/platform/userDataSync/common/userDataSyncStoreService'; 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 { IElectronService } from 'vs/platform/electron/node/electron';
import { LoggerService } from 'vs/platform/log/node/loggerService'; import { LoggerService } from 'vs/platform/log/node/loggerService';
import { UserDataSyncLogService } from 'vs/platform/userDataSync/common/userDataSyncLog'; 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 { KeytarCredentialsService } from 'vs/platform/credentials/node/credentialsService';
import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService'; import { UserDataAutoSyncService } from 'vs/platform/userDataSync/electron-browser/userDataAutoSyncService';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; 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 { NativeStorageService } from 'vs/platform/storage/node/storageService';
import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc'; import { GlobalStorageDatabaseChannelClient } from 'vs/platform/storage/node/storageIpc';
import { IStorageService } from 'vs/platform/storage/common/storage'; import { IStorageService } from 'vs/platform/storage/common/storage';
import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService'; import { GlobalExtensionEnablementService } from 'vs/platform/extensionManagement/common/extensionEnablementService';
import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/userDataSyncEnablementService'; 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 { export interface ISharedProcessConfiguration {
readonly machineId: string; readonly machineId: string;
@@ -188,7 +189,7 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService)); services.set(IDiagnosticsService, new SyncDescriptor(DiagnosticsService));
services.set(ICredentialsService, new SyncDescriptor(KeytarCredentialsService)); 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(IUserDataSyncLogService, new SyncDescriptor(UserDataSyncLogService));
services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main'))); services.set(IUserDataSyncUtilService, new UserDataSyncUtilServiceClient(server.getChannel('userDataSyncUtil', client => client.ctx !== 'main')));
services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService)); services.set(IGlobalExtensionEnablementService, new SyncDescriptor(GlobalExtensionEnablementService));
@@ -214,8 +215,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService); const diagnosticsChannel = new DiagnosticsChannel(diagnosticsService);
server.registerChannel('diagnostics', diagnosticsChannel); server.registerChannel('diagnostics', diagnosticsChannel);
const authTokenService = accessor.get(IUserDataAuthTokenService); const authTokenService = accessor.get(IAuthenticationTokenService);
const authTokenChannel = new UserDataAuthTokenServiceChannel(authTokenService); const authTokenChannel = new AuthenticationTokenServiceChannel(authTokenService);
server.registerChannel('authToken', authTokenChannel); server.registerChannel('authToken', authTokenChannel);
const settingsSyncService = accessor.get(ISettingsSyncService); 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 { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation'; import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry'; import { KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
import { EditorOption } from 'vs/editor/common/config/editorOptions';
const CORE_WEIGHT = KeybindingWeight.EditorCore; 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 namespace CoreEditingCommands {
export abstract class CoreEditingCommand extends EditorCommand { 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) { export const DefaultUndo: UndoCommand = registerCommand(new UndoCommand({ id: 'default:undo', precondition: EditorContextKeys.writable }));
command.register();
}
/** export const Redo: RedoCommand = registerCommand(new RedoCommand({
* A command that will: id: 'redo',
* 1. invoke a command on the focused editor. precondition: EditorContextKeys.writable,
* 2. otherwise, invoke a browser built-in command on the `activeElement`. kbOpts: {
* 3. otherwise, invoke a command on the workbench active editor. weight: CORE_WEIGHT,
*/ kbExpr: EditorContextKeys.textInputFocus,
class EditorOrNativeTextInputCommand extends Command { 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; export const DefaultRedo: RedoCommand = registerCommand(new RedoCommand({ id: 'default:redo', precondition: EditorContextKeys.writable }));
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);
}
}
} }
/** /**
@@ -1743,78 +1831,7 @@ class EditorHandlerCommand extends Command {
} }
} }
registerCommand(new EditorOrNativeTextInputCommand({ registerCommand(new SelectAllCommand());
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));
function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void { function registerOverwritableCommand(handlerId: string, description?: ICommandHandlerDescription): void {
registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId)); registerCommand(new EditorHandlerCommand('default:' + handlerId, handlerId));
@@ -589,14 +589,19 @@ export class ViewLines extends ViewPart implements IVisibleLinesHost<ViewLine>,
if (boxEndY - boxStartY > viewportHeight) { if (boxEndY - boxStartY > viewportHeight) {
// the box is larger than the viewport ... scroll to its top // the box is larger than the viewport ... scroll to its top
newScrollTop = boxStartY; newScrollTop = boxStartY;
} else if (verticalType === viewEvents.VerticalRevealType.NearTop) { } else if (verticalType === viewEvents.VerticalRevealType.NearTop || verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport) {
// We want a gap that is 20% of the viewport, but with a minimum of 5 lines if (verticalType === viewEvents.VerticalRevealType.NearTopIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2); // Box is already in the viewport... do nothing
// Try to scroll just above the box with the desired gap newScrollTop = viewportStartY;
const desiredScrollTop = boxStartY - desiredGapAbove; } else {
// But ensure that the box is not pushed out of viewport // We want a gap that is 20% of the viewport, but with a minimum of 5 lines
const minScrollTop = boxEndY - viewportHeight; const desiredGapAbove = Math.max(5 * this._lineHeight, viewportHeight * 0.2);
newScrollTop = Math.max(minScrollTop, desiredScrollTop); // 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) { } else if (verticalType === viewEvents.VerticalRevealType.Center || verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport) {
if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) { if (verticalType === viewEvents.VerticalRevealType.CenterIfOutsideViewport && viewportStartY <= boxStartY && boxEndY <= viewportEndY) {
// Box is already in the viewport... do nothing // 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 * as strings from 'vs/base/common/strings';
import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer'; import { ILine, RenderedLinesCollection } from 'vs/editor/browser/view/viewLayer';
import { PartFingerprint, PartFingerprints, ViewPart } from 'vs/editor/browser/view/viewPart'; 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 { Range } from 'vs/editor/common/core/range';
import { RGBA8 } from 'vs/editor/common/core/rgba'; import { RGBA8 } from 'vs/editor/common/core/rgba';
import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon'; import { IConfiguration, ScrollType } from 'vs/editor/common/editorCommon';
@@ -91,6 +91,8 @@ class MinimapOptions {
*/ */
public readonly canvasOuterHeight: number; public readonly canvasOuterHeight: number;
public readonly isSampling: boolean;
public readonly editorHeight: number;
public readonly fontScale: number; public readonly fontScale: number;
public readonly minimapLineHeight: number; public readonly minimapLineHeight: number;
public readonly minimapCharWidth: number; public readonly minimapCharWidth: number;
@@ -122,6 +124,8 @@ class MinimapOptions {
this.canvasOuterWidth = layoutInfo.minimapCanvasOuterWidth; this.canvasOuterWidth = layoutInfo.minimapCanvasOuterWidth;
this.canvasOuterHeight = layoutInfo.minimapCanvasOuterHeight; this.canvasOuterHeight = layoutInfo.minimapCanvasOuterHeight;
this.isSampling = layoutInfo.minimapIsSampling;
this.editorHeight = layoutInfo.height;
this.fontScale = layoutInfo.minimapScale; this.fontScale = layoutInfo.minimapScale;
this.minimapLineHeight = layoutInfo.minimapLineHeight; this.minimapLineHeight = layoutInfo.minimapLineHeight;
this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale; this.minimapCharWidth = Constants.BASE_CHAR_WIDTH * this.fontScale;
@@ -154,6 +158,8 @@ class MinimapOptions {
&& this.canvasInnerHeight === other.canvasInnerHeight && this.canvasInnerHeight === other.canvasInnerHeight
&& this.canvasOuterWidth === other.canvasOuterWidth && this.canvasOuterWidth === other.canvasOuterWidth
&& this.canvasOuterHeight === other.canvasOuterHeight && this.canvasOuterHeight === other.canvasOuterHeight
&& this.isSampling === other.isSampling
&& this.editorHeight === other.editorHeight
&& this.fontScale === other.fontScale && this.fontScale === other.fontScale
&& this.minimapLineHeight === other.minimapLineHeight && this.minimapLineHeight === other.minimapLineHeight
&& this.minimapCharWidth === other.minimapCharWidth && this.minimapCharWidth === other.minimapCharWidth
@@ -527,26 +533,24 @@ type SamplingStateEvent = SamplingStateLinesInsertedEvent | SamplingStateLinesDe
class MinimapSamplingState { class MinimapSamplingState {
public static compute(options: IComputedEditorOptions, modelLineCount: number, oldSamplingState: MinimapSamplingState | null): [MinimapSamplingState | null, SamplingStateEvent[]] { public static compute(options: MinimapOptions, viewLineCount: number, oldSamplingState: MinimapSamplingState | null): [MinimapSamplingState | null, SamplingStateEvent[]] {
const minimapOpts = options.get(EditorOption.minimap); if (options.renderMinimap === RenderMinimap.None || !options.isSampling) {
const layoutInfo = options.get(EditorOption.layoutInfo);
if (!minimapOpts.enabled || !layoutInfo.minimapIsSampling) {
return [null, []]; return [null, []];
} }
// ratio is intentionally not part of the layout to avoid the layout changing all the time // ratio is intentionally not part of the layout to avoid the layout changing all the time
// so we need to recompute it again... // so we need to recompute it again...
const pixelRatio = options.get(EditorOption.pixelRatio); const pixelRatio = options.pixelRatio;
const lineHeight = options.get(EditorOption.lineHeight); const lineHeight = options.lineHeight;
const scrollBeyondLastLine = options.get(EditorOption.scrollBeyondLastLine); const scrollBeyondLastLine = options.scrollBeyondLastLine;
const { minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({ const { minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
modelLineCount: modelLineCount, viewLineCount: viewLineCount,
scrollBeyondLastLine: scrollBeyondLastLine, scrollBeyondLastLine: scrollBeyondLastLine,
height: layoutInfo.height, height: options.editorHeight,
lineHeight: lineHeight, lineHeight: lineHeight,
pixelRatio: pixelRatio pixelRatio: pixelRatio
}); });
const ratio = modelLineCount / minimapLineCount; const ratio = viewLineCount / minimapLineCount;
const halfRatio = ratio / 2; const halfRatio = ratio / 2;
if (!oldSamplingState || oldSamplingState.minimapLines.length === 0) { if (!oldSamplingState || oldSamplingState.minimapLines.length === 0) {
@@ -556,7 +560,7 @@ class MinimapSamplingState {
for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) { for (let i = 0, lastIndex = minimapLineCount - 1; i < lastIndex; i++) {
result[i] = Math.round(i * ratio + halfRatio); result[i] = Math.round(i * ratio + halfRatio);
} }
result[minimapLineCount - 1] = modelLineCount; result[minimapLineCount - 1] = viewLineCount;
} }
return [new MinimapSamplingState(ratio, result), []]; return [new MinimapSamplingState(ratio, result), []];
} }
@@ -566,15 +570,15 @@ class MinimapSamplingState {
let result: number[] = []; let result: number[] = [];
let oldIndex = 0; let oldIndex = 0;
let oldDeltaLineCount = 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 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 events: SamplingStateEvent[] = [];
let lastEvent: SamplingStateEvent | null = null; let lastEvent: SamplingStateEvent | null = null;
for (let i = 0; i < minimapLineCount; i++) { for (let i = 0; i < minimapLineCount; i++) {
const fromModelLineNumber = Math.max(minModelLineNumber, Math.round(i * ratio)); const fromViewLineNumber = Math.max(minViewLineNumber, Math.round(i * ratio));
const toModelLineNumber = Math.max(fromModelLineNumber, Math.round((i + 1) * 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) { if (events.length < MAX_EVENT_COUNT) {
const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) { if (lastEvent && lastEvent.type === 'deleted' && lastEvent._oldIndex === oldIndex - 1) {
@@ -588,18 +592,18 @@ class MinimapSamplingState {
oldIndex++; oldIndex++;
} }
let selectedModelLineNumber: number; let selectedViewLineNumber: number;
if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toModelLineNumber) { if (oldIndex < oldLength && oldMinimapLines[oldIndex] <= toViewLineNumber) {
// reuse the old sampled line // reuse the old sampled line
selectedModelLineNumber = oldMinimapLines[oldIndex]; selectedViewLineNumber = oldMinimapLines[oldIndex];
oldIndex++; oldIndex++;
} else { } else {
if (i === 0) { if (i === 0) {
selectedModelLineNumber = 1; selectedViewLineNumber = 1;
} else if (i + 1 === minimapLineCount) { } else if (i + 1 === minimapLineCount) {
selectedModelLineNumber = modelLineCount; selectedViewLineNumber = viewLineCount;
} else { } else {
selectedModelLineNumber = Math.round(i * ratio + halfRatio); selectedViewLineNumber = Math.round(i * ratio + halfRatio);
} }
if (events.length < MAX_EVENT_COUNT) { if (events.length < MAX_EVENT_COUNT) {
const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount; const oldMinimapLineNumber = oldIndex + 1 + oldDeltaLineCount;
@@ -613,8 +617,8 @@ class MinimapSamplingState {
} }
} }
result[i] = selectedModelLineNumber; result[i] = selectedViewLineNumber;
minModelLineNumber = selectedModelLineNumber; minViewLineNumber = selectedViewLineNumber;
} }
if (events.length < MAX_EVENT_COUNT) { if (events.length < MAX_EVENT_COUNT) {
@@ -743,7 +747,7 @@ export class Minimap extends ViewPart implements IMinimapModel {
this._minimapSelections = null; this._minimapSelections = null;
this.options = new MinimapOptions(this._context.configuration, this._context.theme, this.tokensColorTracker); 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._samplingState = samplingState;
this._shouldCheckSampling = false; this._shouldCheckSampling = false;
@@ -787,6 +791,9 @@ export class Minimap extends ViewPart implements IMinimapModel {
return false; return false;
} }
public onFlushed(e: viewEvents.ViewFlushedEvent): boolean { public onFlushed(e: viewEvents.ViewFlushedEvent): boolean {
if (this._samplingState) {
this._shouldCheckSampling = true;
}
return this._actual.onFlushed(); return this._actual.onFlushed();
} }
public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean { public onLinesChanged(e: viewEvents.ViewLinesChangedEvent): boolean {
@@ -898,7 +905,7 @@ export class Minimap extends ViewPart implements IMinimapModel {
this._minimapSelections = null; this._minimapSelections = null;
const wasSampling = Boolean(this._samplingState); 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; this._samplingState = samplingState;
if (wasSampling && this._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 { public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
this._revealRange( this._revealRange(
range, 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); const viewOutgoingEvents = new ViewOutgoingEvents(viewModel);
viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e); viewOutgoingEvents.onDidContentSizeChange = (e) => this._onDidContentSizeChange.fire(e);
viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e); viewOutgoingEvents.onDidScroll = (e) => this._onDidScrollChange.fire(e);
viewOutgoingEvents.onDidGainFocus = () => this._editorTextFocus.setValue(true); viewOutgoingEvents.onDidGainFocus = () => onDidChangeTextFocus(true);
viewOutgoingEvents.onDidLoseFocus = () => this._editorTextFocus.setValue(false); viewOutgoingEvents.onDidLoseFocus = () => onDidChangeTextFocus(false);
viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e); viewOutgoingEvents.onContextMenu = (e) => this._onContextMenu.fire(e);
viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e); viewOutgoingEvents.onMouseDown = (e) => this._onMouseDown.fire(e);
viewOutgoingEvents.onMouseUp = (e) => this._onMouseUp.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._modelData = null;
this._domElement.removeAttribute('data-mode-id'); this._domElement.removeAttribute('data-mode-id');
if (removeDomNode) { if (removeDomNode && this._domElement.contains(removeDomNode)) {
this._domElement.removeChild(removeDomNode); this._domElement.removeChild(removeDomNode);
} }
@@ -830,6 +830,10 @@ export class DiffEditorWidget extends Disposable implements editorBrowser.IDiffE
this.modifiedEditor.revealRangeNearTop(range, scrollType); 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 { public revealRangeAtTop(range: IRange, scrollType: editorCommon.ScrollType = editorCommon.ScrollType.Smooth): void {
this.modifiedEditor.revealRangeAtTop(range, scrollType); this.modifiedEditor.revealRangeAtTop(range, scrollType);
} }
@@ -287,7 +287,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
public options!: ComputedEditorOptions; public options!: ComputedEditorOptions;
private _isDominatedByLongLines: boolean; private _isDominatedByLongLines: boolean;
private _maxLineNumber: number; private _viewLineCount: number;
private _lineNumbersDigitCount: number; private _lineNumbersDigitCount: number;
private _rawOptions: IEditorOptions; private _rawOptions: IEditorOptions;
@@ -299,7 +299,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
this.isSimpleWidget = isSimpleWidget; this.isSimpleWidget = isSimpleWidget;
this._isDominatedByLongLines = false; this._isDominatedByLongLines = false;
this._maxLineNumber = 1; this._viewLineCount = 1;
this._lineNumbersDigitCount = 1; this._lineNumbersDigitCount = 1;
this._rawOptions = deepCloneAndMigrateOptions(_options); this._rawOptions = deepCloneAndMigrateOptions(_options);
@@ -349,7 +349,7 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
fontInfo: this.readConfiguration(bareFontInfo), fontInfo: this.readConfiguration(bareFontInfo),
extraEditorClassName: partialEnv.extraEditorClassName, extraEditorClassName: partialEnv.extraEditorClassName,
isDominatedByLongLines: this._isDominatedByLongLines, isDominatedByLongLines: this._isDominatedByLongLines,
maxLineNumber: this._maxLineNumber, viewLineCount: this._viewLineCount,
lineNumbersDigitCount: this._lineNumbersDigitCount, lineNumbersDigitCount: this._lineNumbersDigitCount,
emptySelectionClipboard: partialEnv.emptySelectionClipboard, emptySelectionClipboard: partialEnv.emptySelectionClipboard,
pixelRatio: partialEnv.pixelRatio, pixelRatio: partialEnv.pixelRatio,
@@ -408,11 +408,19 @@ export abstract class CommonEditorConfiguration extends Disposable implements IC
} }
public setMaxLineNumber(maxLineNumber: number): void { public setMaxLineNumber(maxLineNumber: number): void {
if (this._maxLineNumber === maxLineNumber) { const lineNumbersDigitCount = CommonEditorConfiguration._digitCount(maxLineNumber);
if (this._lineNumbersDigitCount === lineNumbersDigitCount) {
return; return;
} }
this._maxLineNumber = maxLineNumber; this._lineNumbersDigitCount = lineNumbersDigitCount;
this._lineNumbersDigitCount = CommonEditorConfiguration._digitCount(maxLineNumber); this._recomputeOptions();
}
public setViewLineCount(viewLineCount: number): void {
if (this._viewLineCount === viewLineCount) {
return;
}
this._viewLineCount = viewLineCount;
this._recomputeOptions(); this._recomputeOptions();
} }
+11 -11
View File
@@ -678,7 +678,7 @@ export interface IEnvironmentalOptions {
readonly fontInfo: FontInfo; readonly fontInfo: FontInfo;
readonly extraEditorClassName: string; readonly extraEditorClassName: string;
readonly isDominatedByLongLines: boolean; readonly isDominatedByLongLines: boolean;
readonly maxLineNumber: number; readonly viewLineCount: number;
readonly lineNumbersDigitCount: number; readonly lineNumbersDigitCount: number;
readonly emptySelectionClipboard: boolean; readonly emptySelectionClipboard: boolean;
readonly pixelRatio: number; readonly pixelRatio: number;
@@ -1733,7 +1733,7 @@ export interface EditorLayoutInfoComputerEnv {
outerWidth: number; outerWidth: number;
outerHeight: number; outerHeight: number;
lineHeight: number; lineHeight: number;
maxLineNumber: number; viewLineCount: number;
lineNumbersDigitCount: number; lineNumbersDigitCount: number;
typicalHalfwidthCharacterWidth: number; typicalHalfwidthCharacterWidth: number;
maxDigitWidth: number; maxDigitWidth: number;
@@ -1757,7 +1757,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
outerWidth: env.outerWidth, outerWidth: env.outerWidth,
outerHeight: env.outerHeight, outerHeight: env.outerHeight,
lineHeight: env.fontInfo.lineHeight, lineHeight: env.fontInfo.lineHeight,
maxLineNumber: env.maxLineNumber, viewLineCount: env.viewLineCount,
lineNumbersDigitCount: env.lineNumbersDigitCount, lineNumbersDigitCount: env.lineNumbersDigitCount,
typicalHalfwidthCharacterWidth: env.fontInfo.typicalHalfwidthCharacterWidth, typicalHalfwidthCharacterWidth: env.fontInfo.typicalHalfwidthCharacterWidth,
maxDigitWidth: env.fontInfo.maxDigitWidth, maxDigitWidth: env.fontInfo.maxDigitWidth,
@@ -1766,7 +1766,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
} }
public static computeContainedMinimapLineCount(input: { public static computeContainedMinimapLineCount(input: {
modelLineCount: number; viewLineCount: number;
scrollBeyondLastLine: boolean; scrollBeyondLastLine: boolean;
height: number; height: number;
lineHeight: number; lineHeight: number;
@@ -1774,8 +1774,8 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
}): { typicalViewportLineCount: number; extraLinesBeyondLastLine: number; desiredRatio: number; minimapLineCount: number; } { }): { typicalViewportLineCount: number; extraLinesBeyondLastLine: number; desiredRatio: number; minimapLineCount: number; } {
const typicalViewportLineCount = input.height / input.lineHeight; const typicalViewportLineCount = input.height / input.lineHeight;
const extraLinesBeyondLastLine = input.scrollBeyondLastLine ? (typicalViewportLineCount - 1) : 0; const extraLinesBeyondLastLine = input.scrollBeyondLastLine ? (typicalViewportLineCount - 1) : 0;
const desiredRatio = (input.modelLineCount + extraLinesBeyondLastLine) / (input.pixelRatio * input.height); const desiredRatio = (input.viewLineCount + extraLinesBeyondLastLine) / (input.pixelRatio * input.height);
const minimapLineCount = Math.floor(input.modelLineCount / desiredRatio); const minimapLineCount = Math.floor(input.viewLineCount / desiredRatio);
return { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount }; return { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount };
} }
@@ -1863,9 +1863,9 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
let minimapWidthMultiplier: number = 1; let minimapWidthMultiplier: number = 1;
if (minimapMode === 'cover' || minimapMode === 'contain') { if (minimapMode === 'cover' || minimapMode === 'contain') {
const modelLineCount = env.maxLineNumber; const viewLineCount = env.viewLineCount;
const { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({ const { typicalViewportLineCount, extraLinesBeyondLastLine, desiredRatio, minimapLineCount } = EditorLayoutInfoComputer.computeContainedMinimapLineCount({
modelLineCount: modelLineCount, viewLineCount: viewLineCount,
scrollBeyondLastLine: scrollBeyondLastLine, scrollBeyondLastLine: scrollBeyondLastLine,
height: outerHeight, height: outerHeight,
lineHeight: lineHeight, 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 // ratio is intentionally not part of the layout to avoid the layout changing all the time
// when doing sampling // when doing sampling
const ratio = modelLineCount / minimapLineCount; const ratio = viewLineCount / minimapLineCount;
if (ratio > 1) { if (ratio > 1) {
minimapHeightIsEditorHeight = true; minimapHeightIsEditorHeight = true;
@@ -1882,7 +1882,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
minimapLineHeight = 1; minimapLineHeight = 1;
minimapCharWidth = minimapScale / pixelRatio; minimapCharWidth = minimapScale / pixelRatio;
} else { } else {
const effectiveMinimapHeight = Math.ceil((modelLineCount + extraLinesBeyondLastLine) * minimapLineHeight); const effectiveMinimapHeight = Math.ceil((viewLineCount + extraLinesBeyondLastLine) * minimapLineHeight);
if (minimapMode === 'cover' || effectiveMinimapHeight > minimapCanvasInnerHeight) { if (minimapMode === 'cover' || effectiveMinimapHeight > minimapCanvasInnerHeight) {
minimapHeightIsEditorHeight = true; minimapHeightIsEditorHeight = true;
const configuredFontScale = minimapScale; const configuredFontScale = minimapScale;
@@ -1892,7 +1892,7 @@ export class EditorLayoutInfoComputer extends ComputedEditorOption<EditorOption.
minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale); minimapWidthMultiplier = Math.min(2, minimapScale / configuredFontScale);
} }
minimapCharWidth = minimapScale / pixelRatio / minimapWidthMultiplier; 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 { ISelection, Selection, SelectionDirection } from 'vs/editor/common/core/selection';
import * as editorCommon from 'vs/editor/common/editorCommon'; import * as editorCommon from 'vs/editor/common/editorCommon';
import { ITextModel, TrackedRangeStickiness, IModelDeltaDecoration, ICursorStateComputer, IIdentifiedSingleEditOperation, IValidEditOperation } from 'vs/editor/common/model'; 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 * as viewEvents from 'vs/editor/common/view/viewEvents';
import { IViewModel } from 'vs/editor/common/viewModel/viewModel'; import { IViewModel } from 'vs/editor/common/viewModel/viewModel';
import { dispose } from 'vs/base/common/lifecycle'; import { dispose } from 'vs/base/common/lifecycle';
@@ -186,6 +186,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
public context: CursorContext; public context: CursorContext;
private _cursors: CursorCollection; private _cursors: CursorCollection;
private _hasFocus: boolean;
private _isHandling: boolean; private _isHandling: boolean;
private _isDoingComposition: boolean; private _isDoingComposition: boolean;
private _selectionsWhenCompositionStarted: Selection[] | null; 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.context = new CursorContext(this._configuration, this._model, this._viewModel);
this._cursors = new CursorCollection(this.context); this._cursors = new CursorCollection(this.context);
this._hasFocus = false;
this._isHandling = false; this._isHandling = false;
this._isDoingComposition = false; this._isDoingComposition = false;
this._selectionsWhenCompositionStarted = null; this._selectionsWhenCompositionStarted = null;
@@ -215,8 +217,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
return; return;
} }
let hadFlushEvent = e.containsEvent(RawContentChangedType.Flush); this._onModelContentChanged(e);
this._onModelContentChanged(hadFlushEvent);
})); }));
this._register(viewModel.addEventListener((events: viewEvents.ViewEvent[]) => { this._register(viewModel.addEventListener((events: viewEvents.ViewEvent[]) => {
@@ -264,6 +265,10 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
super.dispose(); super.dispose();
} }
public setHasFocus(hasFocus: boolean): void {
this._hasFocus = hasFocus;
}
private _validateAutoClosedActions(): void { private _validateAutoClosedActions(): void {
if (this._autoClosedActions.length > 0) { if (this._autoClosedActions.length > 0) {
let selections: Range[] = this._cursors.getSelections(); 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); 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; this._prevEditOperationType = EditOperationType.Other;
if (hadFlushEvent) { if (hadFlushEvent) {
@@ -403,8 +409,13 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._validateAutoClosedActions(); this._validateAutoClosedActions();
this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null); this._emitStateChangedIfNecessary('model', CursorChangeReason.ContentFlush, null);
} else { } else {
const selectionsFromMarkers = this._cursors.readSelectionFromMarkers(); if (this._hasFocus && e.resultingSelection && e.resultingSelection.length > 0) {
this.setStates('modelChange', CursorChangeReason.RecoverFromMarkers, CursorState.fromModelSelections(selectionsFromMarkers)); 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); const oldState = new CursorModelState(this._model, this);
let cursorChangeReason = CursorChangeReason.NotSet; let cursorChangeReason = CursorChangeReason.NotSet;
if (handlerId !== H.Undo && handlerId !== H.Redo) { this._cursors.stopTrackingSelections();
// TODO@Alex: if the undo/redo stack contains non-null selections
// it would also be OK to stop tracking selections here
this._cursors.stopTrackingSelections();
}
// ensure valid state on all cursors // ensure valid state on all cursors
this._cursors.ensureValidState(); this._cursors.ensureValidState();
@@ -734,16 +741,6 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._cut(); this._cut();
break; 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: case H.ExecuteCommand:
this._externalExecuteCommand(<editorCommon.ICommand>payload); this._externalExecuteCommand(<editorCommon.ICommand>payload);
break; break;
@@ -762,9 +759,7 @@ export class Cursor extends viewEvents.ViewEventEmitter implements ICursors {
this._isHandling = false; this._isHandling = false;
if (handlerId !== H.Undo && handlerId !== H.Redo) { this._cursors.startTrackingSelections();
this._cursors.startTrackingSelections();
}
this._validateAutoClosedActions(); this._validateAutoClosedActions();
+7 -4
View File
@@ -154,6 +154,7 @@ export interface IConfiguration extends IDisposable {
readonly options: IComputedEditorOptions; readonly options: IComputedEditorOptions;
setMaxLineNumber(maxLineNumber: number): void; setMaxLineNumber(maxLineNumber: number): void;
setViewLineCount(viewLineCount: number): void;
updateOptions(newOptions: IEditorOptions): void; updateOptions(newOptions: IEditorOptions): void;
getRawOptions(): IEditorOptions; getRawOptions(): IEditorOptions;
observeReferenceElement(dimension?: IDimension): void; observeReferenceElement(dimension?: IDimension): void;
@@ -466,6 +467,12 @@ export interface IEditor {
*/ */
revealRangeNearTop(range: IRange, scrollType?: ScrollType): void; 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. * Directly trigger a handler or an editor action.
* @param source The source of the call. * @param source The source of the call.
@@ -671,9 +678,5 @@ export const Handler = {
CompositionStart: 'compositionStart', CompositionStart: 'compositionStart',
CompositionEnd: 'compositionEnd', CompositionEnd: 'compositionEnd',
Paste: 'paste', Paste: 'paste',
Cut: 'cut', Cut: 'cut',
Undo: 'undo',
Redo: 'redo',
}; };
+19 -2
View File
@@ -379,6 +379,13 @@ export interface IValidEditOperation {
forceMoveMarkers: boolean; forceMoveMarkers: boolean;
} }
/**
* @internal
*/
export interface IValidEditOperations {
operations: IValidEditOperation[];
}
/** /**
* A callback that can compute the cursor state after applying a series of edit operations. * 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[]; 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. * 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. * This can have dire consequences on the undo stack! See @pushEOL for the preferred way.
*/ */
setEOL(eol: EndOfLineSequence): void; 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`. * Undo edit operations until the first previous stop point created by `pushStackElement`.
* The inverse edit operations will be pushed on the redo stack. * The inverse edit operations will be pushed on the redo stack.
* @internal * @internal
*/ */
undo(): Selection[] | null; undo(): void;
/** /**
* Is there anything in the undo stack? * 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. * The inverse edit operations will be pushed on the undo stack.
* @internal * @internal
*/ */
redo(): Selection[] | null; redo(): void;
/** /**
* Is there anything in the redo stack? * 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. * 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 { onUnexpectedError } from 'vs/base/common/errors';
import { Selection } from 'vs/editor/common/core/selection'; 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 { 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 { class EditStackElement implements IUndoRedoElement {
operations: IValidEditOperation[];
}
interface IStackElement { public readonly label: string;
readonly beforeVersionId: number; private _isOpen: boolean;
readonly beforeCursorState: Selection[] | null; private readonly _model: TextModel;
readonly afterCursorState: Selection[] | null; private readonly _beforeVersionId: number;
readonly afterVersionId: number; private readonly _beforeCursorState: Selection[];
private _afterVersionId: number;
private _afterCursorState: Selection[] | null;
private _edits: IValidEditOperations[];
undo(model: TextModel): void; public get resources(): readonly URI[] {
redo(model: TextModel): void; return [this._model.uri];
}
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 undo(model: TextModel): void { constructor(model: TextModel, beforeVersionId: number, beforeCursorState: Selection[], afterVersionId: number, afterCursorState: Selection[] | null, operations: IValidEditOperation[]) {
// Apply all operations in reverse order this.label = nls.localize('edit', "Typing");
for (let i = this.editOperations.length - 1; i >= 0; i--) { this._isOpen = true;
this.editOperations[i] = { this._model = model;
operations: model.applyEdits(this.editOperations[i].operations) this._beforeVersionId = beforeVersionId;
}; this._beforeCursorState = beforeCursorState;
} this._afterVersionId = afterVersionId;
this._afterCursorState = afterCursorState;
this._edits = [{ operations: operations }];
} }
public redo(model: TextModel): void { public isOpen(): boolean {
// Apply all operations return this._isOpen;
for (let i = 0; i < this.editOperations.length; i++) { }
this.editOperations[i] = {
operations: model.applyEdits(this.editOperations[i].operations) 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(); const eol = model.getEOL();
if (eol === '\n') { if (eol === '\n') {
return EndOfLineSequence.LF; return EndOfLineSequence.LF;
@@ -66,32 +77,40 @@ function getModelEOL(model: TextModel): EndOfLineSequence {
} }
} }
class EOLStackElement implements IStackElement { class EOLStackElement implements IUndoRedoElement {
public readonly beforeVersionId: number;
public readonly beforeCursorState: Selection[] | null;
public readonly afterCursorState: Selection[] | null;
public afterVersionId: number;
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) { public get resources(): readonly URI[] {
this.beforeVersionId = beforeVersionId; return [this._model.uri];
this.beforeCursorState = null;
this.afterCursorState = null;
this.afterVersionId = -1;
this.eol = setEOL;
} }
public undo(model: TextModel): void { constructor(model: TextModel, beforeVersionId: number, afterVersionId: number, eol: EndOfLineSequence) {
let redoEOL = getModelEOL(model); this.label = nls.localize('eol', "Change End Of Line Sequence");
model.setEOL(this.eol); this._model = model;
this.eol = redoEOL; this._beforeVersionId = beforeVersionId;
this._afterVersionId = afterVersionId;
this._eol = eol;
} }
public redo(model: TextModel): void { undo(ctx: IUndoRedoContext): void {
let undoEOL = getModelEOL(model); const redoEOL = getModelEOL(this._model);
model.setEOL(this.eol); this._model._setEOL(this._eol, true, false, this._beforeVersionId, null);
this.eol = undoEOL; 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 { export class EditStack {
private readonly model: TextModel; private readonly _model: TextModel;
private currentOpenStackElement: IStackElement | null; private readonly _undoRedoService: IUndoRedoService;
private past: IStackElement[];
private future: IStackElement[];
constructor(model: TextModel) { constructor(model: TextModel, undoRedoService: IUndoRedoService) {
this.model = model; this._model = model;
this.currentOpenStackElement = null; this._undoRedoService = undoRedoService;
this.past = [];
this.future = [];
} }
public pushStackElement(): void { public pushStackElement(): void {
if (this.currentOpenStackElement !== null) { const lastElement = this._undoRedoService.getLastElement(this._model.uri);
this.past.push(this.currentOpenStackElement); if (lastElement && lastElement instanceof EditStackElement) {
this.currentOpenStackElement = null; lastElement.close();
} }
} }
public clear(): void { public clear(): void {
this.currentOpenStackElement = null; this._undoRedoService.removeElements(this._model.uri);
this.past = [];
this.future = [];
} }
public pushEOL(eol: EndOfLineSequence): void { public pushEOL(eol: EndOfLineSequence): void {
// No support for parallel universes :( const beforeVersionId = this._model.getAlternativeVersionId();
this.future = []; const inverseEOL = getModelEOL(this._model);
this._model.setEOL(eol);
const afterVersionId = this._model.getAlternativeVersionId();
if (this.currentOpenStackElement) { const lastElement = this._undoRedoService.getLastElement(this._model.uri);
this.pushStackElement(); if (lastElement && lastElement instanceof EditStackElement) {
lastElement.close();
} }
this._undoRedoService.pushElement(new EOLStackElement(this._model, inverseEOL, beforeVersionId, afterVersionId));
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();
} }
public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null { public pushEditOperation(beforeCursorState: Selection[], editOperations: IIdentifiedSingleEditOperation[], cursorStateComputer: ICursorStateComputer | null): Selection[] | null {
// No support for parallel universes :( const beforeVersionId = this._model.getAlternativeVersionId();
this.future = []; const inverseEditOperations = this._model.applyEdits(editOperations);
const afterVersionId = this._model.getAlternativeVersionId();
const afterCursorState = EditStack._computeCursorState(cursorStateComputer, inverseEditOperations);
let stackElement: EditStackElement | null = null; const lastElement = this._undoRedoService.getLastElement(this._model.uri);
if (lastElement && lastElement instanceof EditStackElement && lastElement.isOpen()) {
if (this.currentOpenStackElement) { lastElement.append(inverseEditOperations, afterVersionId, afterCursorState);
if (this.currentOpenStackElement instanceof EditStackElement) { } else {
stackElement = this.currentOpenStackElement; this._undoRedoService.pushElement(new EditStackElement(this._model, beforeVersionId, beforeCursorState, afterVersionId, afterCursorState, inverseEditOperations));
} else {
this.pushStackElement();
}
} }
if (!this.currentOpenStackElement) { return afterCursorState;
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;
} }
private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null { private static _computeCursorState(cursorStateComputer: ICursorStateComputer | null, inverseEditOperations: IValidEditOperation[]): Selection[] | null {
@@ -182,62 +177,4 @@ export class EditStack {
return null; 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 { Color } from 'vs/base/common/color';
import { Constants } from 'vs/base/common/uint'; import { Constants } from 'vs/base/common/uint';
import { EditorTheme } from 'vs/editor/common/view/viewContext'; 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() { function createTextBufferBuilder() {
return new PieceTreeTextBufferBuilder(); 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 { 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 { 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 id: string;
public readonly isForSimpleWidget: boolean; public readonly isForSimpleWidget: boolean;
private readonly _associatedResource: URI; private readonly _associatedResource: URI;
private readonly _undoRedoService: IUndoRedoService;
private _attachedEditorCount: number; private _attachedEditorCount: number;
private _buffer: model.ITextBuffer; private _buffer: model.ITextBuffer;
private _options: model.TextModelResolvedOptions; private _options: model.TextModelResolvedOptions;
@@ -268,7 +271,7 @@ export class TextModel extends Disposable implements model.ITextModel {
private readonly _isTooLargeForTokenization: boolean; private readonly _isTooLargeForTokenization: boolean;
//#region Editing //#region Editing
private _commandManager: EditStack; private readonly _commandManager: EditStack;
private _isUndoing: boolean; private _isUndoing: boolean;
private _isRedoing: boolean; private _isRedoing: boolean;
private _trimAutoWhitespaceLines: number[] | null; private _trimAutoWhitespaceLines: number[] | null;
@@ -293,7 +296,13 @@ export class TextModel extends Disposable implements model.ITextModel {
private readonly _tokenization: TextModelTokenization; private readonly _tokenization: TextModelTokenization;
//#endregion //#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(); super();
// Generate a new unique model id // Generate a new unique model id
@@ -305,6 +314,7 @@ export class TextModel extends Disposable implements model.ITextModel {
} else { } else {
this._associatedResource = associatedResource; this._associatedResource = associatedResource;
} }
this._undoRedoService = undoRedoService;
this._attachedEditorCount = 0; this._attachedEditorCount = 0;
this._buffer = createTextBuffer(source, creationOptions.defaultEOL); this._buffer = createTextBuffer(source, creationOptions.defaultEOL);
@@ -347,7 +357,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._decorations = Object.create(null); this._decorations = Object.create(null);
this._decorationsTree = new DecorationsTrees(); this._decorationsTree = new DecorationsTrees();
this._commandManager = new EditStack(this); this._commandManager = new EditStack(this, undoRedoService);
this._isUndoing = false; this._isUndoing = false;
this._isRedoing = false; this._isRedoing = false;
this._trimAutoWhitespaceLines = null; this._trimAutoWhitespaceLines = null;
@@ -362,6 +372,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._onWillDispose.fire(); this._onWillDispose.fire();
this._languageRegistryListener.dispose(); this._languageRegistryListener.dispose();
this._tokenization.dispose(); this._tokenization.dispose();
this._undoRedoService.removeElements(this.uri);
this._isDisposed = true; this._isDisposed = true;
super.dispose(); super.dispose();
this._isDisposing = false; this._isDisposing = false;
@@ -436,7 +447,7 @@ export class TextModel extends Disposable implements model.ITextModel {
this._decorationsTree = new DecorationsTrees(); this._decorationsTree = new DecorationsTrees();
// Destroy my edit history and settings // Destroy my edit history and settings
this._commandManager = new EditStack(this); this._commandManager.clear();
this._trimAutoWhitespaceLines = null; this._trimAutoWhitespaceLines = null;
this._emitContentChangedEvent( 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 { private _onBeforeEOLChange(): void {
// Ensure all decorations get their `range` set. // Ensure all decorations get their `range` set.
const versionId = this.getVersionId(); const versionId = this.getVersionId();
@@ -1272,18 +1298,37 @@ export class TextModel extends Disposable implements model.ITextModel {
return this._commandManager.pushEditOperation(beforeCursorState, editOperations, cursorStateComputer); 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[] { public applyEdits(rawOperations: model.IIdentifiedSingleEditOperation[]): model.IValidEditOperation[] {
try { try {
this._onDidChangeDecorations.beginDeferredEmit(); this._onDidChangeDecorations.beginDeferredEmit();
this._eventEmitter.beginDeferredEmit(); this._eventEmitter.beginDeferredEmit();
return this._applyEdits(this._validateEditOperations(rawOperations)); return this._doApplyEdits(this._validateEditOperations(rawOperations));
} finally { } finally {
this._eventEmitter.endDeferredEmit(); this._eventEmitter.endDeferredEmit();
this._onDidChangeDecorations.endDeferredEmit(); this._onDidChangeDecorations.endDeferredEmit();
} }
} }
private _applyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] { private _doApplyEdits(rawOperations: model.ValidAnnotatedEditOperation[]): model.IValidEditOperation[] {
const oldLineCount = this._buffer.getLineCount(); const oldLineCount = this._buffer.getLineCount();
const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace); const result = this._buffer.applyEdits(rawOperations, this._options.trimAutoWhitespace);
@@ -1364,62 +1409,20 @@ export class TextModel extends Disposable implements model.ITextModel {
return result.reverseEdits; return result.reverseEdits;
} }
private _undo(): Selection[] | null { public undo(): void {
this._isUndoing = true; this._undoRedoService.undo(this.uri);
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 canUndo(): boolean { public canUndo(): boolean {
return this._commandManager.canUndo(); return this._undoRedoService.canUndo(this.uri);
} }
private _redo(): Selection[] | null { public redo(): void {
this._isRedoing = true; this._undoRedoService.redo(this.uri);
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 canRedo(): boolean { public canRedo(): boolean {
return this._commandManager.canRedo(); return this._undoRedoService.canRedo(this.uri);
} }
//#endregion //#endregion
@@ -3191,10 +3194,11 @@ export class DidChangeContentEmitter extends Disposable {
this._deferredCnt++; this._deferredCnt++;
} }
public endDeferredEmit(): void { public endDeferredEmit(resultingSelection: Selection[] | null = null): void {
this._deferredCnt--; this._deferredCnt--;
if (this._deferredCnt === 0) { if (this._deferredCnt === 0) {
if (this._deferredEvent !== null) { if (this._deferredEvent !== null) {
this._deferredEvent.rawContentChangedEvent.resultingSelection = resultingSelection;
const e = this._deferredEvent; const e = this._deferredEvent;
this._deferredEvent = null; this._deferredEvent = null;
this._fastEmitter.fire(e); this._fastEmitter.fire(e);
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { IRange } from 'vs/editor/common/core/range'; 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. * 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 readonly isRedoing: boolean;
public resultingSelection: Selection[] | null;
constructor(changes: ModelRawChange[], versionId: number, isUndoing: boolean, isRedoing: boolean) { constructor(changes: ModelRawChange[], versionId: number, isUndoing: boolean, isRedoing: boolean) {
this.changes = changes; this.changes = changes;
this.versionId = versionId; this.versionId = versionId;
this.isUndoing = isUndoing; this.isUndoing = isUndoing;
this.isRedoing = isRedoing; this.isRedoing = isRedoing;
this.resultingSelection = null;
} }
public containsEvent(type: RawContentChangedType): boolean { 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 { SparseEncodedTokens, MultilineTokens2 } from 'vs/editor/common/model/tokensStore';
import { IThemeService } from 'vs/platform/theme/common/themeService'; import { IThemeService } from 'vs/platform/theme/common/themeService';
import { ILogService, LogLevel } from 'vs/platform/log/common/log'; import { ILogService, LogLevel } from 'vs/platform/log/common/log';
import { IUndoRedoService } from 'vs/platform/undoRedo/common/undoRedo';
export interface IEditorSemanticHighlightingOptions { export interface IEditorSemanticHighlightingOptions {
enabled?: boolean; enabled?: boolean;
@@ -103,6 +104,7 @@ export class ModelServiceImpl extends Disposable implements IModelService {
private readonly _configurationService: IConfigurationService; private readonly _configurationService: IConfigurationService;
private readonly _configurationServiceSubscription: IDisposable; private readonly _configurationServiceSubscription: IDisposable;
private readonly _resourcePropertiesService: ITextResourcePropertiesService; private readonly _resourcePropertiesService: ITextResourcePropertiesService;
private readonly _undoRedoService: IUndoRedoService;
private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>()); private readonly _onModelAdded: Emitter<ITextModel> = this._register(new Emitter<ITextModel>());
public readonly onModelAdded: Event<ITextModel> = this._onModelAdded.event; public readonly onModelAdded: Event<ITextModel> = this._onModelAdded.event;
@@ -126,11 +128,13 @@ export class ModelServiceImpl extends Disposable implements IModelService {
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService, @ITextResourcePropertiesService resourcePropertiesService: ITextResourcePropertiesService,
@IThemeService themeService: IThemeService, @IThemeService themeService: IThemeService,
@ILogService logService: ILogService @ILogService logService: ILogService,
@IUndoRedoService undoRedoService: IUndoRedoService
) { ) {
super(); super();
this._configurationService = configurationService; this._configurationService = configurationService;
this._resourcePropertiesService = resourcePropertiesService; this._resourcePropertiesService = resourcePropertiesService;
this._undoRedoService = undoRedoService;
this._models = {}; this._models = {};
this._modelCreationOptionsByLanguageAndResource = Object.create(null); 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 { private _createModelData(value: string | ITextBufferFactory, languageIdentifier: LanguageIdentifier, resource: URI | undefined, isForSimpleWidget: boolean): ModelData {
// create & save the model // create & save the model
const options = this.getCreationOptions(languageIdentifier.language, resource, isForSimpleWidget); 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); const modelId = MODEL_ID(model.uri);
if (this._models[modelId]) { if (this._models[modelId]) {
+1
View File
@@ -195,6 +195,7 @@ export const enum VerticalRevealType {
Top = 3, Top = 3,
Bottom = 4, Bottom = 4,
NearTop = 5, NearTop = 5,
NearTopIfOutsideViewport = 6,
} }
export class ViewRevealRangeRequestEvent { export class ViewRevealRangeRequestEvent {
@@ -33,6 +33,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
private readonly configuration: IConfiguration; private readonly configuration: IConfiguration;
private readonly model: ITextModel; private readonly model: ITextModel;
private readonly _tokenizeViewportSoon: RunOnceScheduler; private readonly _tokenizeViewportSoon: RunOnceScheduler;
private readonly _updateConfigurationViewLineCount: RunOnceScheduler;
private hasFocus: boolean; private hasFocus: boolean;
private viewportStartLine: number; private viewportStartLine: number;
private viewportStartLineTrackedRange: string | null; private viewportStartLineTrackedRange: string | null;
@@ -56,6 +57,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this.configuration = configuration; this.configuration = configuration;
this.model = model; this.model = model;
this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50)); this._tokenizeViewportSoon = this._register(new RunOnceScheduler(() => this.tokenizeViewport(), 50));
this._updateConfigurationViewLineCount = this._register(new RunOnceScheduler(() => this._updateConfigurationViewLineCountNow(), 0));
this.hasFocus = false; this.hasFocus = false;
this.viewportStartLine = -1; this.viewportStartLine = -1;
this.viewportStartLineTrackedRange = null; this.viewportStartLineTrackedRange = null;
@@ -130,6 +132,8 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
this._endEmit(); this._endEmit();
} }
})); }));
this._updateConfigurationViewLineCountNow();
} }
public dispose(): void { 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); this.viewportStartLineTrackedRange = this.model._setTrackedRange(this.viewportStartLineTrackedRange, null, TrackedRangeStickiness.NeverGrowsWhenTypingAtEdges);
} }
private _updateConfigurationViewLineCountNow(): void {
this.configuration.setViewLineCount(this.lines.getViewLineCount());
}
public tokenizeViewport(): void { public tokenizeViewport(): void {
const linesViewportData = this.viewLayout.getLinesViewportData(); const linesViewportData = this.viewLayout.getLinesViewportData();
const startPosition = this.coordinatesConverter.convertViewPositionToModelPosition(new Position(linesViewportData.startLineNumber, 1)); 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... // Never change the scroll position from 0 to something else...
restorePreviousViewportStart = true; restorePreviousViewportStart = true;
} }
this._updateConfigurationViewLineCount.schedule();
} }
if (e.hasChanged(EditorOption.readOnly)) { 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 // Update the configuration and reset the centered view line
this.viewportStartLine = -1; this.viewportStartLine = -1;
this.configuration.setMaxLineNumber(this.model.getLineCount()); this.configuration.setMaxLineNumber(this.model.getLineCount());
this._updateConfigurationViewLineCountNow();
// Recover viewport // Recover viewport
if (!this.hasFocus && this.model.getAttachedEditorCount() >= 2 && this.viewportStartLineTrackedRange) { if (!this.hasFocus && this.model.getAttachedEditorCount() >= 2 && this.viewportStartLineTrackedRange) {
@@ -358,6 +369,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
} finally { } finally {
this._endEmit(); this._endEmit();
} }
this._updateConfigurationViewLineCount.schedule();
} }
})); }));
@@ -387,6 +399,7 @@ export class ViewModel extends viewEvents.ViewEventEmitter implements IViewModel
} finally { } finally {
this._endEmit(); this._endEmit();
} }
this._updateConfigurationViewLineCount.schedule();
} }
public getVisibleRanges(): Range[] { public getVisibleRanges(): Range[] {
@@ -15,6 +15,7 @@ import { TextModel } from 'vs/editor/common/model/textModel';
import { FindModelBoundToEditorModel } from 'vs/editor/contrib/find/findModel'; import { FindModelBoundToEditorModel } from 'vs/editor/contrib/find/findModel';
import { FindReplaceState } from 'vs/editor/contrib/find/findState'; import { FindReplaceState } from 'vs/editor/contrib/find/findState';
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
suite('FindModel', () => { suite('FindModel', () => {
@@ -44,7 +45,7 @@ suite('FindModel', () => {
const factory = ptBuilder.finish(); const factory = ptBuilder.finish();
withTestCodeEditor([], 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) (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) => { registerThemingParticipant((theme, collector) => {
const foldBackground = theme.getColor(foldBackgroundBackground); const foldBackground = theme.getColor(foldBackgroundBackground);
@@ -167,7 +167,7 @@ abstract class SymbolNavigationAction extends EditorAction {
resource: reference.uri, resource: reference.uri,
options: { options: {
selection: Range.collapseToStart(range), selection: Range.collapseToStart(range),
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport
} }
}, editor, sideBySide); }, editor, sideBySide);
@@ -128,7 +128,7 @@ class SymbolNavigationService implements ISymbolNavigationService {
resource: reference.uri, resource: reference.uri,
options: { options: {
selection: Range.collapseToStart(reference.range), selection: Range.collapseToStart(reference.range),
selectionRevealType: TextEditorSelectionRevealType.CenterIfOutsideViewport selectionRevealType: TextEditorSelectionRevealType.NearTopIfOutsideViewport
} }
}, source).finally(() => { }, source).finally(() => {
this._ignoreEditorChange = false; this._ignoreEditorChange = false;
@@ -317,7 +317,7 @@ suite('Editor Contrib - Line Operations', () => {
assert.equal(model.getLineContent(1), 'one'); assert.equal(model.getLineContent(1), 'one');
assert.deepEqual(editor.getSelection(), new Selection(1, 1, 1, 1)); 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.equal(model.getLineContent(1), 'Typing some text here on line one');
assert.deepEqual(editor.getSelection(), new Selection(1, 31, 1, 31)); 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.equal(model.getLineContent(1), 'hello my dear world');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); 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.equal(model.getLineContent(1), 'hello my dear');
assert.deepEqual(editor.getSelection(), new Selection(1, 14, 1, 14)); 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) new Selection(2, 4, 2, 4)
]); ]);
editor.trigger('tests', Handler.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.deepEqual(editor.getSelections(), [ assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3),
new Selection(1, 6, 1, 6), new Selection(1, 6, 1, 6),
new Selection(3, 4, 3, 4) new Selection(3, 4, 3, 4)
]); ]);
editor.trigger('tests', Handler.Redo, {}); CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.deepEqual(editor.getSelections(), [ assert.deepEqual(editor.getSelections(), [
new Selection(1, 3, 1, 3), new Selection(1, 3, 1, 3),
new Selection(2, 4, 2, 4) 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 { TestTextResourcePropertiesService } from 'vs/editor/test/common/services/modelService.test';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { NullLogService } from 'vs/platform/log/common/log'; import { NullLogService } from 'vs/platform/log/common/log';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
class MockJSMode extends MockMode { class MockJSMode extends MockMode {
@@ -47,7 +48,7 @@ suite('SmartSelect', () => {
setup(() => { setup(() => {
const configurationService = new TestConfigurationService(); 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(); mode = new MockJSMode();
}); });
@@ -11,6 +11,7 @@ import { IModelDeltaDecoration } from 'vs/editor/common/model';
import { SuggestController } from 'vs/editor/contrib/suggest/suggestController'; import { SuggestController } from 'vs/editor/contrib/suggest/suggestController';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
import { domEvent } from 'vs/base/browser/event'; import { domEvent } from 'vs/base/browser/event';
import { domContentLoaded } from 'vs/base/browser/dom';
export class SuggestRangeHighlighter { export class SuggestRangeHighlighter {
@@ -101,10 +102,12 @@ const shiftKey = new class ShiftKey extends Emitter<boolean> {
constructor() { constructor() {
super(); super();
this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey)); domContentLoaded().then(() => {
this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false)); this._subscriptions.add(domEvent(document.body, 'keydown')(e => this.isPressed = e.shiftKey));
this._subscriptions.add(domEvent(document.body, 'mouseleave')(() => this.isPressed = false)); this._subscriptions.add(domEvent(document.body, 'keyup')(() => this.isPressed = false));
this._subscriptions.add(domEvent(document.body, 'blur')(() => 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 { get isPressed(): boolean {
@@ -13,6 +13,7 @@ import { CursorWordEndLeft, CursorWordEndLeftSelect, CursorWordEndRight, CursorW
import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor'; import { withTestCodeEditor } from 'vs/editor/test/browser/testCodeEditor';
import { Handler } from 'vs/editor/common/editorCommon'; import { Handler } from 'vs/editor/common/editorCommon';
import { Cursor } from 'vs/editor/common/controller/cursor'; import { Cursor } from 'vs/editor/common/controller/cursor';
import { CoreEditingCommands } from 'vs/editor/browser/controller/coreCommands';
suite('WordOperations', () => { suite('WordOperations', () => {
@@ -216,7 +217,7 @@ suite('WordOperations', () => {
assert.equal(editor.getValue(), 'foo qbar baz'); assert.equal(editor.getValue(), 'foo qbar baz');
cursorCommand(cursor, Handler.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(editor.getValue(), 'foo bar baz'); 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 { AccessibilityService } from 'vs/platform/accessibility/common/accessibilityService';
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService'; import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
import { BrowserClipboardService } from 'vs/platform/clipboard/browser/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 { export interface IEditorOverrideServices {
[index: string]: any; [index: string]: any;
@@ -150,7 +152,9 @@ export module StaticServices {
export const logService = define(ILogService, () => new NullLogService()); 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))); 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); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert9'); 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'); 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'); 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'); 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'); 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'); 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'); 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', () => { test('issue #23539: Setting model EOL isn\'t undoable', () => {
usingCursor({ withTestCodeEditor([
text: [ 'Hello',
'Hello', 'world'
'world' ], {}, (editor, cursor) => {
] const model = editor.getModel()!;
}, (model, cursor) => {
assertCursor(cursor, new Position(1, 1)); assertCursor(cursor, new Position(1, 1));
model.setEOL(EndOfLineSequence.LF); model.setEOL(EndOfLineSequence.LF);
assert.equal(model.getValue(), 'Hello\nworld'); assert.equal(model.getValue(), 'Hello\nworld');
@@ -1276,7 +1276,7 @@ suite('Editor Controller - Regression tests', () => {
model.pushEOL(EndOfLineSequence.CRLF); model.pushEOL(EndOfLineSequence.CRLF);
assert.equal(model.getValue(), 'Hello\r\nworld'); assert.equal(model.getValue(), 'Hello\r\nworld');
cursorCommand(cursor, H.Undo); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), 'Hello\nworld'); assert.equal(model.getValue(), 'Hello\nworld');
}); });
}); });
@@ -1301,7 +1301,7 @@ suite('Editor Controller - Regression tests', () => {
cursorCommand(cursor, H.Type, { text: '%' }, 'keyboard'); cursorCommand(cursor, H.Type, { text: '%' }, 'keyboard');
assert.equal(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1'); assert.equal(model.getValue(EndOfLinePreference.LF), '%\'%👁\'', 'assert1');
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2'); assert.equal(model.getValue(EndOfLinePreference.LF), '\'👁\'', 'assert2');
}); });
@@ -1327,39 +1327,39 @@ suite('Editor Controller - Regression tests', () => {
assert.equal(model.getLineContent(1), 'Hello world'); assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12)); assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world '); assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13)); assertCursor(cursor, new Position(1, 13));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world'); assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12)); assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello'); assert.equal(model.getLineContent(1), 'Hello');
assertCursor(cursor, new Position(1, 6)); assertCursor(cursor, new Position(1, 6));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), ''); assert.equal(model.getLineContent(1), '');
assertCursor(cursor, new Position(1, 1)); assertCursor(cursor, new Position(1, 1));
cursorCommand(cursor, H.Redo, {}); CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello'); assert.equal(model.getLineContent(1), 'Hello');
assertCursor(cursor, new Position(1, 6)); assertCursor(cursor, new Position(1, 6));
cursorCommand(cursor, H.Redo, {}); CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world'); assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12)); assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Redo, {}); CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world '); assert.equal(model.getLineContent(1), 'Hello world ');
assertCursor(cursor, new Position(1, 13)); assertCursor(cursor, new Position(1, 13));
cursorCommand(cursor, H.Redo, {}); CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world'); assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12)); assertCursor(cursor, new Position(1, 12));
cursorCommand(cursor, H.Redo, {}); CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), 'Hello world'); assert.equal(model.getLineContent(1), 'Hello world');
assertCursor(cursor, new Position(1, 12)); assertCursor(cursor, new Position(1, 12));
}); });
@@ -1735,21 +1735,21 @@ suite('Editor Controller - Regression tests', () => {
'\t just some text' '\t just some text'
].join('\n'), '001'); ].join('\n'), '001');
cursorCommand(cursor, H.Undo); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [ assert.equal(model.getValue(), [
' some lines', ' some lines',
' and more lines', ' and more lines',
' just some text', ' just some text',
].join('\n'), '002'); ].join('\n'), '002');
cursorCommand(cursor, H.Undo); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [ assert.equal(model.getValue(), [
'some lines', 'some lines',
'and more lines', 'and more lines',
'just some text', 'just some text',
].join('\n'), '003'); ].join('\n'), '003');
cursorCommand(cursor, H.Undo); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getValue(), [ assert.equal(model.getValue(), [
'some lines', 'some lines',
'and more 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', () => { test('issue #9675: Undo/Redo adds a stop in between CHN Characters', () => {
usingCursor({ withTestCodeEditor([], {}, (editor, cursor) => {
text: [ const model = editor.getModel()!;
]
}, (model, cursor) => {
assertCursor(cursor, new Position(1, 1)); assertCursor(cursor, new Position(1, 1));
// Typing sennsei in Japanese - Hiragana // Typing sennsei in Japanese - Hiragana
@@ -1957,7 +1955,7 @@ suite('Editor Controller - Regression tests', () => {
assert.equal(model.getLineContent(1), 'せんせい'); assert.equal(model.getLineContent(1), 'せんせい');
assertCursor(cursor, new Position(1, 5)); assertCursor(cursor, new Position(1, 5));
cursorCommand(cursor, H.Undo); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(1), ''); assert.equal(model.getLineContent(1), '');
assertCursor(cursor, new Position(1, 1)); assertCursor(cursor, new Position(1, 1));
}); });
@@ -2138,7 +2136,7 @@ suite('Editor Controller - Regression tests', () => {
}], () => [new Selection(1, 1, 1, 1)]); }], () => [new Selection(1, 1, 1, 1)]);
assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!'); 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!'); assert.equal(model.getValue(EndOfLinePreference.LF), 'Hello world!');
}); });
@@ -2229,12 +2227,12 @@ suite('Editor Controller - Regression tests', () => {
new Selection(1, 5, 1, 5), new Selection(1, 5, 1, 5),
]); ]);
cursorCommand(cursor, H.Undo, null, 'keyboard'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assertCursor(cursor, [ assertCursor(cursor, [
new Selection(1, 4, 1, 4), new Selection(1, 4, 1, 4),
]); ]);
cursorCommand(cursor, H.Redo, null, 'keyboard'); CoreEditingCommands.Redo.runEditorCommand(null, editor, null);
assertCursor(cursor, [ assertCursor(cursor, [
new Selection(1, 5, 1, 5), new Selection(1, 5, 1, 5),
]); ]);
@@ -2263,7 +2261,7 @@ suite('Editor Controller - Regression tests', () => {
new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1),
]); ]);
cursorCommand(cursor, H.Undo, null, 'keyboard'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assertCursor(cursor, [ assertCursor(cursor, [
new Selection(1, 1, 1, 1), new Selection(1, 1, 1, 1),
]); ]);
@@ -2378,49 +2376,49 @@ suite('Editor Controller - Cursor Configuration', () => {
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 1) }); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 1) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null); CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' My Second Line123'); assert.equal(model.getLineContent(2), ' My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 2 // Tab on column 2
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 2) }); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 2) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null); CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'M y Second Line123'); 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 // Tab on column 3
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 3) }); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 3) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null); CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 4 // Tab on column 4
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 4) }); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 4) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null); CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
cursorCommand(cursor, H.Undo, null, 'keyboard'); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
// Tab on column 5 // Tab on column 5
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) }); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null); CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My S econd Line123'); 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 // Tab on column 5
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) }); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 5) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null); CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My S econd Line123'); 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 // Tab on column 13
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 13) }); CoreNavigationCommands.MoveTo.runCoreEditorCommand(cursor, { position: new Position(2, 13) });
CoreEditingCommands.Tab.runEditorCommand(null, editor, null); CoreEditingCommands.Tab.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'My Second Li ne123'); 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 // Tab on column 14
assert.equal(model.getLineContent(2), 'My Second Line123'); assert.equal(model.getLineContent(2), 'My Second Line123');
@@ -2774,7 +2772,7 @@ suite('Editor Controller - Cursor Configuration', () => {
assert.equal(model.getLineContent(2), 'a '); assert.equal(model.getLineContent(2), 'a ');
// Undo DeleteLeft - get us back to original indentation // Undo DeleteLeft - get us back to original indentation
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' a '); assert.equal(model.getLineContent(2), ' a ');
// Nothing is broken when cursor is in (1,1) // Nothing is broken when cursor is in (1,1)
@@ -2859,22 +2857,22 @@ suite('Editor Controller - Cursor Configuration', () => {
CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null); CoreEditingCommands.DeleteLeft.runEditorCommand(null, editor, null);
assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert10'); 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'); 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'); 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'); 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'); 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'); 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'); assert.equal(model.getValue(EndOfLinePreference.LF), 'x', 'assert16');
}); });
@@ -2895,7 +2893,7 @@ suite('Editor Controller - Cursor Configuration', () => {
const beforeVersion = model.getVersionId(); const beforeVersion = model.getVersionId();
const beforeAltVersion = model.getAlternativeVersionId(); const beforeAltVersion = model.getAlternativeVersionId();
cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard'); cursorCommand(cursor, H.Type, { text: 'Hello' }, 'keyboard');
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
const afterVersion = model.getVersionId(); const afterVersion = model.getVersionId();
const afterAltVersion = model.getAlternativeVersionId(); const afterAltVersion = model.getAlternativeVersionId();
@@ -4263,7 +4261,7 @@ suite('autoClosingPairs', () => {
moveTo(cursor, lineNumber, column); moveTo(cursor, lineNumber, column);
cursorCommand(cursor, H.Type, { text: chr }, 'keyboard'); cursorCommand(cursor, H.Type, { text: chr }, 'keyboard');
assert.deepEqual(model.getLineContent(lineNumber), expected, message); assert.deepEqual(model.getLineContent(lineNumber), expected, message);
cursorCommand(cursor, H.Undo); model.undo();
} }
test('open parens: default', () => { test('open parens: default', () => {
@@ -5347,11 +5345,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A fir line'); assert.equal(model.getLineContent(1), 'A fir line');
assertCursor(cursor, new Selection(1, 6, 1, 6)); 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'); assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8)); 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'); assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3)); assertCursor(cursor, new Selection(1, 3, 1, 3));
}); });
@@ -5376,11 +5374,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(1), 'A firstine'); assert.equal(model.getLineContent(1), 'A firstine');
assertCursor(cursor, new Selection(1, 8, 1, 8)); 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'); assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8)); 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'); assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3)); assertCursor(cursor, new Selection(1, 3, 1, 3));
}); });
@@ -5410,11 +5408,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'Second line'); assert.equal(model.getLineContent(2), 'Second line');
assertCursor(cursor, new Selection(2, 7, 2, 7)); assertCursor(cursor, new Selection(2, 7, 2, 7));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' line'); assert.equal(model.getLineContent(2), ' line');
assertCursor(cursor, new Selection(2, 1, 2, 1)); 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'); assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 8, 2, 8)); assertCursor(cursor, new Selection(2, 8, 2, 8));
}); });
@@ -5448,11 +5446,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), ''); assert.equal(model.getLineContent(2), '');
assertCursor(cursor, new Selection(2, 1, 2, 1)); assertCursor(cursor, new Selection(2, 1, 2, 1));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), ' line'); assert.equal(model.getLineContent(2), ' line');
assertCursor(cursor, new Selection(2, 1, 2, 1)); 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'); assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 8, 2, 8)); assertCursor(cursor, new Selection(2, 8, 2, 8));
}); });
@@ -5479,11 +5477,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'Another text'); assert.equal(model.getLineContent(2), 'Another text');
assertCursor(cursor, new Selection(2, 13, 2, 13)); assertCursor(cursor, new Selection(2, 13, 2, 13));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another '); assert.equal(model.getLineContent(2), 'Another ');
assertCursor(cursor, new Selection(2, 9, 2, 9)); 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'); assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 9, 2, 9)); assertCursor(cursor, new Selection(2, 9, 2, 9));
}); });
@@ -5515,11 +5513,11 @@ suite('Undo stops', () => {
assert.equal(model.getLineContent(2), 'An'); assert.equal(model.getLineContent(2), 'An');
assertCursor(cursor, new Selection(2, 3, 2, 3)); assertCursor(cursor, new Selection(2, 3, 2, 3));
cursorCommand(cursor, H.Undo, {}); CoreEditingCommands.Undo.runEditorCommand(null, editor, null);
assert.equal(model.getLineContent(2), 'Another '); assert.equal(model.getLineContent(2), 'Another ');
assertCursor(cursor, new Selection(2, 9, 2, 9)); 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'); assert.equal(model.getLineContent(2), 'Another line');
assertCursor(cursor, new Selection(2, 9, 2, 9)); 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'); assert.equal(model.getLineContent(1), 'A first and interesting line');
assertCursor(cursor, new Selection(1, 24, 1, 24)); 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'); assert.equal(model.getLineContent(1), 'A first and line');
assertCursor(cursor, new Selection(1, 12, 1, 12)); 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'); assert.equal(model.getLineContent(1), 'A first line');
assertCursor(cursor, new Selection(1, 8, 1, 8)); 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'); assert.equal(model.getLineContent(1), 'A line');
assertCursor(cursor, new Selection(1, 3, 1, 3)); 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); let editor = <TestCodeEditor>createTestCodeEditor(options);
editor.getCursor()!.setHasFocus(true);
callback(editor, editor.getCursor()!); callback(editor, editor.getCursor()!);
editor.dispose(); editor.dispose();
@@ -12,7 +12,7 @@ import { IModelContentChangedEvent } from 'vs/editor/common/model/textModelEvent
import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils'; import { assertSyncedModels, testApplyEditsWithSyncedModels } from 'vs/editor/test/common/model/editableTextModelTestUtils';
function createEditableTextModelFromString(text: string): TextModel { 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', () => { 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 { 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); model.setEOL(EndOfLineSequence.LF);
assertLineMapping(model, 'model'); assertLineMapping(model, 'model');
@@ -106,7 +106,7 @@ suite('ModelLinesTokens', () => {
function testApplyEdits(initial: IBufferLineState[], edits: IEdit[], expected: IBufferLineState[]): void { function testApplyEdits(initial: IBufferLineState[], edits: IEdit[], expected: IBufferLineState[]): void {
const initialText = initial.map(el => el.text).join('\n'); 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++) { for (let lineIndex = 0; lineIndex < initial.length; lineIndex++) {
const lineTokens = initial[lineIndex].tokens; const lineTokens = initial[lineIndex].tokens;
const lineTextLength = model.getLineMaxColumn(lineIndex + 1) - 1; const lineTextLength = model.getLineMaxColumn(lineIndex + 1) - 1;
@@ -442,7 +442,7 @@ suite('ModelLinesTokens', () => {
} }
test('insertion on empty line', () => { 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)]); const tokens = TestToken.toTokens([new TestToken(0, 1)]);
LineTokens.convertToEndOffset(tokens, model.getLineMaxColumn(1) - 1); LineTokens.convertToEndOffset(tokens, model.getLineMaxColumn(1) - 1);
model.setLineTokens(1, tokens); model.setLineTokens(1, tokens);
@@ -72,7 +72,7 @@ suite('TextModelWithTokens', () => {
brackets: brackets brackets: brackets
}); });
let model = new TextModel( let model = TextModel.createFromString(
contents.join('\n'), contents.join('\n'),
TextModel.DEFAULT_CREATION_OPTIONS, TextModel.DEFAULT_CREATION_OPTIONS,
languageIdentifier languageIdentifier
@@ -18,6 +18,7 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService'; import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService'; import { TestThemeService } from 'vs/platform/theme/test/common/testThemeService';
import { NullLogService } from 'vs/platform/log/common/log'; import { NullLogService } from 'vs/platform/log/common/log';
import { UndoRedoService } from 'vs/platform/undoRedo/common/undoRedoService';
const GENERATE_TESTS = false; const GENERATE_TESTS = false;
@@ -29,7 +30,7 @@ suite('ModelService', () => {
configService.setUserConfiguration('files', { 'eol': '\n' }); configService.setUserConfiguration('files', { 'eol': '\n' });
configService.setUserConfiguration('files', { 'eol': '\r\n' }, URI.file(platform.isWindows ? 'c:\\myroot' : '/myroot')); 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(() => { teardown(() => {
@@ -80,7 +80,7 @@ suite('Editor ViewLayout - EditorLayoutProvider', () => {
outerWidth: input.outerWidth, outerWidth: input.outerWidth,
outerHeight: input.outerHeight, outerHeight: input.outerHeight,
lineHeight: input.lineHeight, lineHeight: input.lineHeight,
maxLineNumber: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1, viewLineCount: input.maxLineNumber || Math.pow(10, input.lineNumbersDigitCount) - 1,
lineNumbersDigitCount: input.lineNumbersDigitCount, lineNumbersDigitCount: input.lineNumbersDigitCount,
typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth, typicalHalfwidthCharacterWidth: input.typicalHalfwidthCharacterWidth,
maxDigitWidth: input.maxDigitWidth, maxDigitWidth: input.maxDigitWidth,
+5 -1
View File
@@ -923,7 +923,11 @@ var AMDLoader;
var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest(); var hashDataNow = _this._crypto.createHash('md5').update(scriptSource, 'utf8').digest();
if (!hashData.equals(hashDataNow)) { if (!hashData.equals(hashDataNow)) {
moduleManager.getConfig().onError(new Error("FAILED TO VERIFY CACHED DATA, deleting stale '" + cachedDataPath + "' now, but a RESTART IS REQUIRED")); 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()))); }, Math.ceil(5000 * (1 + Math.random())));
}; };
+5
View File
@@ -2307,6 +2307,11 @@ declare namespace monaco.editor {
* optimized for viewing a code definition. * optimized for viewing a code definition.
*/ */
revealRangeNearTop(range: IRange, scrollType?: ScrollType): void; 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. * Directly trigger a handler or an editor action.
* @param source The source of the call. * @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. * 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 { Emitter, Event } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle'; 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; _serviceBrand: any;
@@ -38,3 +51,4 @@ export class UserDataAuthTokenService extends Disposable implements IUserDataAut
this._onTokenFailed.fire(); 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; scope?: ConfigurationScope;
included?: boolean; included?: boolean;
tags?: string[]; tags?: string[];
disallowSyncIgnore?: boolean;
} }
export interface IConfigurationExtensionInfo { 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. * 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, 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 { export interface ITextEditorOptions extends IEditorOptions {
+2
View File
@@ -59,6 +59,8 @@ export interface IssueReporterData extends WindowData {
enabledExtensions: IssueReporterExtensionData[]; enabledExtensions: IssueReporterExtensionData[];
issueType?: IssueType; issueType?: IssueType;
extensionId?: string; extensionId?: string;
readonly issueTitle?: string;
readonly issueBody?: string;
} }
export interface ISettingSearchResult { export interface ISettingSearchResult {
@@ -35,6 +35,7 @@ export enum RemoteAuthorityResolverErrorCode {
Unknown = 'Unknown', Unknown = 'Unknown',
NotAvailable = 'NotAvailable', NotAvailable = 'NotAvailable',
TemporarilyNotAvailable = 'TemporarilyNotAvailable', TemporarilyNotAvailable = 'TemporarilyNotAvailable',
NoResolverFound = 'NoResolverFound'
} }
export class RemoteAuthorityResolverError extends Error { export class RemoteAuthorityResolverError extends Error {
@@ -50,10 +51,11 @@ export class RemoteAuthorityResolverError extends Error {
} }
public static isTemporarilyNotAvailable(err: any): boolean { public static isTemporarilyNotAvailable(err: any): boolean {
if (err instanceof RemoteAuthorityResolverError) { return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable;
return err._code === RemoteAuthorityResolverErrorCode.TemporarilyNotAvailable; }
}
return false; public static isNoResolverFound(err: any): boolean {
return (err instanceof RemoteAuthorityResolverError) && err._code === RemoteAuthorityResolverErrorCode.NoResolverFound;
} }
public readonly _message: string | undefined; 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. * 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. * The label of the undo/redo element.
@@ -43,7 +43,7 @@ export interface IUndoRedoElement {
* Invalidate the edits concerning `resource`. * Invalidate the edits concerning `resource`.
* i.e. the undo/redo stack for that particular resource has been destroyed. * i.e. the undo/redo stack for that particular resource has been destroyed.
*/ */
invalidate(resource: URI): boolean; invalidate(resource: URI): void;
} }
export interface IUndoRedoService { export interface IUndoRedoService {
@@ -7,11 +7,12 @@ import { IUndoRedoService, IUndoRedoElement } from 'vs/platform/undoRedo/common/
import { URI } from 'vs/base/common/uri'; import { URI } from 'vs/base/common/uri';
import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources'; import { getComparisonKey as uriGetComparisonKey } from 'vs/base/common/resources';
import { onUnexpectedError } from 'vs/base/common/errors'; import { onUnexpectedError } from 'vs/base/common/errors';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
class StackElement { class StackElement {
public readonly actual: IUndoRedoElement; public readonly actual: IUndoRedoElement;
public readonly label: string; public readonly label: string;
public readonly resources: URI[]; public readonly resources: readonly URI[];
public readonly strResources: string[]; public readonly strResources: string[];
constructor(actual: IUndoRedoElement) { constructor(actual: IUndoRedoElement) {
@@ -179,7 +180,7 @@ export class UndoRedoService implements IUndoRedoService {
return false; return false;
} }
redo(resource: URI): void { public redo(resource: URI): void {
const strResource = uriGetComparisonKey(resource); const strResource = uriGetComparisonKey(resource);
if (!this._editStacks.has(strResource)) { if (!this._editStacks.has(strResource)) {
return; 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 { IStringDictionary } from 'vs/base/common/collections';
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
const BACK_UP_MAX_AGE = 1000 * 60 * 60 * 24 * 30; /* 30 days */
type SyncSourceClassification = { type SyncSourceClassification = {
source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true }; source?: { classification: 'SystemMetaData', purpose: 'FeatureInsight', isMeasurement: true };
}; };
@@ -68,6 +70,7 @@ export abstract class AbstractSynchroniser extends Disposable {
this.syncFolder = joinPath(environmentService.userDataSyncHome, source); this.syncFolder = joinPath(environmentService.userDataSyncHome, source);
this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`); this.lastSyncResource = joinPath(this.syncFolder, `.lastSync${source}.json`);
this.cleanUpDelayer = new ThrottledDelayer(50); this.cleanUpDelayer = new ThrottledDelayer(50);
this.cleanUpBackup();
} }
protected setStatus(status: SyncStatus): void { protected setStatus(status: SyncStatus): void {
@@ -195,9 +198,24 @@ export abstract class AbstractSynchroniser extends Disposable {
private async cleanUpBackup(): Promise<void> { private async cleanUpBackup(): Promise<void> {
const stat = await this.fileService.resolve(this.syncFolder); const stat = await this.fileService.resolve(this.syncFolder);
if (stat.children) { if (stat.children) {
const all = stat.children.filter(stat => stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)).sort(); const toDelete = stat.children.filter(stat => {
const toDelete = all.slice(0, Math.max(0, all.length - 9)); if (stat.isFile && /^\d{8}T\d{6}$/.test(stat.name)) {
await Promise.all(toDelete.map(stat => this.fileService.del(stat.resource))); 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 uuids: Map<string, string> = new Map<string, string>();
const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } }; const addUUID = (identifier: IExtensionIdentifier) => { if (identifier.uuid) { uuids.set(identifier.id.toLowerCase(), identifier.uuid); } };
localExtensions.forEach(({ identifier }) => addUUID(identifier)); localExtensions.forEach(({ identifier }) => addUUID(identifier));
@@ -37,10 +43,12 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
lastSyncExtensions.forEach(({ identifier }) => addUUID(identifier)); 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 uuid = extension.identifier.uuid || uuids.get(extension.identifier.id.toLowerCase());
const key = uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`; return uuid ? `uuid:${uuid}` : `id:${extension.identifier.id.toLowerCase()}`;
map.set(key, extension); };
const addExtensionToMap = (map: Map<string, ISyncExtension>, extension: ISyncExtension) => {
map.set(getKey(extension), extension);
return map; return map;
}; };
const localExtensionsMap = localExtensions.reduce(addExtensionToMap, new Map<string, ISyncExtension>()); 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 baseToLocal = compare(lastSyncExtensionsMap, localExtensionsMap, ignoredExtensionsSet);
const baseToRemote = compare(lastSyncExtensionsMap, remoteExtensionsMap, 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 = { const massagedExtension: ISyncExtension = {
identifier: { identifier: {
id: extension.identifier.id, id: extension.identifier.id,
uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined uuid: startsWith(key, 'uuid:') ? key.substring('uuid:'.length) : undefined
}, },
enabled: extension.enabled,
}; };
if (extension.disabled) {
massagedExtension.disabled = true;
}
if (extension.version) { if (extension.version) {
massagedExtension.version = extension.version; massagedExtension.version = extension.version;
} }
@@ -90,25 +101,25 @@ export function merge(localExtensions: ISyncExtension[], remoteExtensions: ISync
if (baseToLocal.added.has(key)) { if (baseToLocal.added.has(key)) {
// Is different from local to remote // Is different from local to remote
if (localToRemote.updated.has(key)) { if (localToRemote.updated.has(key)) {
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
} }
} else { } else {
// Add to local // Add to local
added.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); added.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
} }
} }
// Remotely updated extensions // Remotely updated extensions
for (const key of values(baseToRemote.updated)) { for (const key of values(baseToRemote.updated)) {
// Update in local always // Update in local always
updated.push(massageSyncExtension(remoteExtensionsMap.get(key)!, key)); updated.push(massageOutgoingExtension(remoteExtensionsMap.get(key)!, key));
} }
// Locally added extensions // Locally added extensions
for (const key of values(baseToLocal.added)) { for (const key of values(baseToLocal.added)) {
// Not there in remote // Not there in remote
if (!baseToRemote.added.has(key)) { 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 not updated in remote
if (!baseToRemote.updated.has(key)) { 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 remoteChanges = compare(remoteExtensionsMap, newRemoteExtensionsMap, new Set<string>());
const remote = remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0 ? values(newRemoteExtensionsMap) : null; if (remoteChanges.added.size > 0 || remoteChanges.updated.size > 0 || remoteChanges.removed.size > 0) {
return { added, removed, updated, remote }; 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> } { 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 fromExtension = from!.get(key)!;
const toExtension = to.get(key); const toExtension = to.get(key);
if (!toExtension if (!toExtension
|| fromExtension.enabled !== toExtension.enabled || fromExtension.disabled !== toExtension.disabled
|| fromExtension.version !== toExtension.version || fromExtension.version !== toExtension.version
) { ) {
updated.add(key); updated.add(key);
@@ -14,17 +14,19 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
import { localize } from 'vs/nls'; import { localize } from 'vs/nls';
import { merge } from 'vs/platform/userDataSync/common/extensionsMerge'; import { merge } from 'vs/platform/userDataSync/common/extensionsMerge';
import { isNonEmptyArray } from 'vs/base/common/arrays'; 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 { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { VSBuffer } from 'vs/base/common/buffer';
interface ISyncPreviewResult { interface ISyncPreviewResult {
readonly localExtensions: ISyncExtension[];
readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: ILastSyncUserData | null;
readonly added: ISyncExtension[]; readonly added: ISyncExtension[];
readonly removed: IExtensionIdentifier[]; readonly removed: IExtensionIdentifier[];
readonly updated: ISyncExtension[]; readonly updated: ISyncExtension[];
readonly remote: ISyncExtension[] | null; readonly remote: ISyncExtension[] | null;
readonly remoteUserData: IRemoteUserData;
readonly skippedExtensions: ISyncExtension[]; readonly skippedExtensions: ISyncExtension[];
readonly lastSyncUserData: ILastSyncUserData | null;
} }
interface ILastSyncUserData extends IRemoteUserData { interface ILastSyncUserData extends IRemoteUserData {
@@ -34,7 +36,7 @@ interface ILastSyncUserData extends IRemoteUserData {
export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser { export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUserDataSynchroniser {
readonly resourceKey: ResourceKey = 'extensions'; readonly resourceKey: ResourceKey = 'extensions';
protected readonly version: number = 1; protected readonly version: number = 2;
constructor( constructor(
@IEnvironmentService environmentService: IEnvironmentService, @IEnvironmentService environmentService: IEnvironmentService,
@@ -75,9 +77,9 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
if (remoteUserData.syncData !== null) { if (remoteUserData.syncData !== null) {
const localExtensions = await this.getLocalExtensions(); const localExtensions = await this.getLocalExtensions();
const remoteExtensions: ISyncExtension[] = JSON.parse(remoteUserData.syncData.content); const remoteExtensions = this.parseExtensions(remoteUserData.syncData);
const { added, updated, remote } = merge(localExtensions, remoteExtensions, [], [], this.getIgnoredExtensions()); const { added, updated, remote, removed } = merge(localExtensions, remoteExtensions, localExtensions, [], this.getIgnoredExtensions());
await this.apply({ added, removed: [], updated, remote, remoteUserData, skippedExtensions: [], lastSyncUserData }); await this.apply({ added, removed, updated, remote, remoteUserData, localExtensions, skippedExtensions: [], lastSyncUserData });
} }
// No remote exists to pull // 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 { added, removed, updated, remote } = merge(localExtensions, null, null, [], this.getIgnoredExtensions());
const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>(); const lastSyncUserData = await this.getLastSyncUserData<ILastSyncUserData>();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); 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.'); this.logService.info('Extensions: Finished pushing extensions.');
} finally { } finally {
@@ -165,8 +167,8 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
} }
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> { private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: ILastSyncUserData | null): Promise<ISyncPreviewResult> {
const remoteExtensions: ISyncExtension[] = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null; const remoteExtensions: ISyncExtension[] | null = remoteUserData.syncData ? this.parseExtensions(remoteUserData.syncData) : null;
const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? JSON.parse(lastSyncUserData.syncData!.content) : null; const lastSyncExtensions: ISyncExtension[] | null = lastSyncUserData ? this.parseExtensions(lastSyncUserData.syncData!) : null;
const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : []; const skippedExtensions: ISyncExtension[] = lastSyncUserData ? lastSyncUserData.skippedExtensions || [] : [];
const localExtensions = await this.getLocalExtensions(); 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()); 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() { private getIgnoredExtensions() {
return this.configurationService.getValue<string[]>('sync.ignoredExtensions') || []; 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; 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) { 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); 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 // Builtin Extension: Sync only enablement state
if (installedExtension && installedExtension.type === ExtensionType.System) { if (installedExtension && installedExtension.type === ExtensionType.System) {
if (e.enabled) { if (e.disabled) {
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 {
this.logService.trace('Extensions: Disabling extension...', e.identifier.id); this.logService.trace('Extensions: Disabling extension...', e.identifier.id);
await this.extensionEnablementService.disableExtension(e.identifier); await this.extensionEnablementService.disableExtension(e.identifier);
this.logService.info('Extensions: Disabled extension', e.identifier.id); 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); removeFromSkipped.push(e.identifier);
return; return;
@@ -252,14 +257,14 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version); const extension = await this.extensionGalleryService.getCompatibleExtension(e.identifier, e.version);
if (extension) { if (extension) {
try { try {
if (e.enabled) { if (e.disabled) {
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 {
this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version); this.logService.trace('Extensions: Disabling extension...', e.identifier.id, extension.version);
await this.extensionEnablementService.disableExtension(extension.identifier); await this.extensionEnablementService.disableExtension(extension.identifier);
this.logService.info('Extensions: Disabled extension', e.identifier.id, extension.version); 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 // Install only if the extension does not exist
if (!installedExtension || installedExtension.manifest.version !== extension.version) { if (!installedExtension || installedExtension.manifest.version !== extension.version) {
@@ -293,11 +298,33 @@ export class ExtensionsSynchroniser extends AbstractSynchroniser implements IUse
return newSkippedExtensions; 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[]> { private async getLocalExtensions(): Promise<ISyncExtension[]> {
const installedExtensions = await this.extensionManagementService.getInstalled(); const installedExtensions = await this.extensionManagementService.getInstalled();
const disabledExtensions = await this.extensionEnablementService.getDisabledExtensions(); const disabledExtensions = this.extensionEnablementService.getDisabledExtensions();
return installedExtensions 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 { interface ISyncPreviewResult {
readonly local: IGlobalState | undefined; readonly local: IGlobalState | undefined;
readonly remote: IGlobalState | undefined; readonly remote: IGlobalState | undefined;
readonly localUserData: IGlobalState;
readonly remoteUserData: IRemoteUserData; readonly remoteUserData: IRemoteUserData;
readonly lastSyncUserData: IRemoteUserData | null; readonly lastSyncUserData: IRemoteUserData | null;
} }
@@ -59,8 +60,9 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
if (remoteUserData.syncData !== null) { if (remoteUserData.syncData !== null) {
const localUserData = await this.getLocalGlobalState();
const local: IGlobalState = JSON.parse(remoteUserData.syncData.content); 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 // 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.logService.info('UI State: Started pushing UI State...');
this.setStatus(SyncStatus.Syncing); this.setStatus(SyncStatus.Syncing);
const remote = await this.getLocalGlobalState(); const localUserData = await this.getLocalGlobalState();
const lastSyncUserData = await this.getLastSyncUserData(); const lastSyncUserData = await this.getLastSyncUserData();
const remoteUserData = await this.getRemoteUserData(lastSyncUserData); 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.'); this.logService.info('UI State: Finished pushing UI State.');
} finally { } finally {
@@ -152,10 +154,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState); 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; const hasChanges = local || remote;
@@ -166,6 +168,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
if (local) { if (local) {
// update local // update local
this.logService.trace('UI State: Updating local ui state...'); this.logService.trace('UI State: Updating local ui state...');
await this.backupLocal(VSBuffer.fromString(JSON.stringify(localUserData)));
await this.writeLocalGlobalState(local); await this.writeLocalGlobalState(local);
this.logService.info('UI State: Updated local ui state'); this.logService.info('UI State: Updated local ui state');
} }
@@ -260,9 +260,7 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
if (content !== null) { if (content !== null) {
if (this.hasErrors(content)) { this.validateContent(content);
throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source);
}
if (hasLocalChanged) { if (hasLocalChanged) {
this.logService.trace('Settings: Updating local settings...'); this.logService.trace('Settings: Updating local settings...');
@@ -317,21 +315,14 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
if (remoteSettingsSyncContent) { if (remoteSettingsSyncContent) {
const localContent: string = fileContent ? fileContent.value.toString() : '{}'; const localContent: string = fileContent ? fileContent.value.toString() : '{}';
this.validateContent(localContent);
// No action when there are errors this.logService.trace('Settings: Merging remote settings with local settings...');
if (this.hasErrors(localContent)) { const result = merge(localContent, remoteSettingsSyncContent.settings, lastSettingsSyncContent ? lastSettingsSyncContent.settings : null, getIgnoredSettings(this.configurationService), resolvedConflicts, formattingOptions);
throw new UserDataSyncError(localize('errorInvalidSettings', "Unable to sync settings as there are errors/warning in settings file."), UserDataSyncErrorCode.LocalInvalidContent, this.source); content = result.localContent || result.remoteContent;
} hasLocalChanged = result.localContent !== null;
hasRemoteChanged = result.remoteContent !== null;
else { hasConflicts = result.hasConflicts;
this.logService.trace('Settings: Merging remote settings with local settings...'); conflictSettings = result.conflictsSettings;
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 // First time syncing to remote
@@ -364,4 +355,10 @@ export class SettingsSynchroniser extends AbstractJsonFileSynchroniser implement
} }
return null; 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 { timeout, Delayer } from 'vs/base/common/async';
import { Event, Emitter } from 'vs/base/common/event'; import { Event, Emitter } from 'vs/base/common/event';
import { Disposable } from 'vs/base/common/lifecycle'; 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 { export class UserDataAutoSyncService extends Disposable implements IUserDataAutoSyncService {
@@ -16,19 +17,19 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
private successiveFailures: number = 0; private successiveFailures: number = 0;
private readonly syncDelayer: Delayer<void>; private readonly syncDelayer: Delayer<void>;
private readonly _onError: Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._register(new Emitter<{ code: UserDataSyncErrorCode, source?: SyncSource }>()); private readonly _onError: Emitter<UserDataSyncError> = this._register(new Emitter<UserDataSyncError>());
readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }> = this._onError.event; readonly onError: Event<UserDataSyncError> = this._onError.event;
constructor( constructor(
@IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService, @IUserDataSyncEnablementService private readonly userDataSyncEnablementService: IUserDataSyncEnablementService,
@IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService, @IUserDataSyncService private readonly userDataSyncService: IUserDataSyncService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
@IUserDataAuthTokenService private readonly userDataAuthTokenService: IUserDataAuthTokenService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService,
) { ) {
super(); super();
this.updateEnablement(false, true); this.updateEnablement(false, true);
this.syncDelayer = this._register(new Delayer<void>(0)); 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(Event.any<any>(userDataSyncService.onDidChangeStatus)(() => this.updateEnablement(true, true)));
this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => this.updateEnablement(true, false))); this._register(this.userDataSyncEnablementService.onDidChangeEnablement(() => this.updateEnablement(true, false)));
this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync())); this._register(this.userDataSyncEnablementService.onDidChangeResourceEnablement(() => this.triggerAutoSync()));
@@ -61,27 +62,23 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
await this.userDataSyncService.sync(); await this.userDataSyncService.sync();
this.resetFailures(); this.resetFailures();
} catch (e) { } 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: Sync is turned off in the cloud.');
this.logService.info('Auto Sync: Resetting the local sync state.'); this.logService.info('Auto Sync: Resetting the local sync state.');
await this.userDataSyncService.resetLocal(); await this.userDataSyncService.resetLocal();
this.logService.info('Auto Sync: Completed resetting the local sync state.'); this.logService.info('Auto Sync: Completed resetting the local sync state.');
if (auto) { if (auto) {
return this.userDataSyncEnablementService.setEnablement(false); this.userDataSyncEnablementService.setEnablement(false);
this._onError.fire(error);
return;
} else { } else {
return this.sync(loop, auto); return this.sync(loop, auto);
} }
} }
if (e instanceof UserDataSyncError && e.code === UserDataSyncErrorCode.SessionExpired) { this.logService.error(error);
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.successiveFailures++; this.successiveFailures++;
this._onError.fire(e instanceof UserDataSyncError ? { code: e.code, source: e.source } : { code: UserDataSyncErrorCode.Unknown }); this._onError.fire(error);
} }
if (loop) { if (loop) {
await timeout(1000 * 60 * 5); await timeout(1000 * 60 * 5);
@@ -95,7 +92,7 @@ export class UserDataAutoSyncService extends Disposable implements IUserDataAuto
private async isAutoSyncEnabled(): Promise<boolean> { private async isAutoSyncEnabled(): Promise<boolean> {
return this.userDataSyncEnablementService.isEnabled() return this.userDataSyncEnablementService.isEnabled()
&& this.userDataSyncService.status !== SyncStatus.Uninitialized && this.userDataSyncService.status !== SyncStatus.Uninitialized
&& !!(await this.userDataAuthTokenService.getToken()); && !!(await this.authTokenService.getToken());
} }
private resetFailures(): void { private resetFailures(): void {
@@ -38,6 +38,7 @@ export interface ISyncConfiguration {
export function registerConfiguration(): IDisposable { export function registerConfiguration(): IDisposable {
const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings'; const ignoredSettingsSchemaId = 'vscode://schemas/ignoredSettings';
const ignoredExtensionsSchemaId = 'vscode://schemas/ignoredExtensions';
const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration); const configurationRegistry = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration);
configurationRegistry.registerConfiguration({ configurationRegistry.registerConfiguration({
id: 'sync', id: 'sync',
@@ -84,14 +85,11 @@ export function registerConfiguration(): IDisposable {
'sync.ignoredExtensions': { 'sync.ignoredExtensions': {
'type': 'array', '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."), '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: { $ref: ignoredExtensionsSchemaId,
type: 'string',
pattern: EXTENSION_IDENTIFIER_PATTERN,
errorMessage: localize('app.extension.identifier.errorMessage', "Expected format '${publisher}.${name}'. Example: 'vscode.csharp'.")
},
'default': [], 'default': [],
'scope': ConfigurationScope.APPLICATION, 'scope': ConfigurationScope.APPLICATION,
uniqueItems: true uniqueItems: true,
disallowSyncIgnore: true
}, },
'sync.ignoredSettings': { 'sync.ignoredSettings': {
'type': 'array', 'type': 'array',
@@ -100,12 +98,13 @@ export function registerConfiguration(): IDisposable {
'scope': ConfigurationScope.APPLICATION, 'scope': ConfigurationScope.APPLICATION,
$ref: ignoredSettingsSchemaId, $ref: ignoredSettingsSchemaId,
additionalProperties: true, additionalProperties: true,
uniqueItems: true uniqueItems: true,
disallowSyncIgnore: true
} }
} }
}); });
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
const registerIgnoredSettingsSchema = () => { const registerIgnoredSettingsSchema = () => {
const jsonRegistry = Registry.as<IJSONContributionRegistry>(JSONExtensions.JSONContribution);
const ignoredSettingsSchema: IJSONSchema = { const ignoredSettingsSchema: IJSONSchema = {
items: { items: {
type: 'string', type: 'string',
@@ -114,6 +113,11 @@ export function registerConfiguration(): IDisposable {
}; };
jsonRegistry.registerSchema(ignoredSettingsSchemaId, ignoredSettingsSchema); 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()); return configurationRegistry.onDidUpdateConfiguration(() => registerIgnoredSettingsSchema());
} }
@@ -210,7 +214,7 @@ export class UserDataSyncStoreError extends UserDataSyncError { }
export interface ISyncExtension { export interface ISyncExtension {
identifier: IExtensionIdentifier; identifier: IExtensionIdentifier;
version?: string; version?: string;
enabled: boolean; disabled?: boolean;
} }
export interface IGlobalState { export interface IGlobalState {
@@ -283,6 +287,9 @@ export interface IUserDataSyncService {
readonly onDidChangeLocal: Event<void>; readonly onDidChangeLocal: Event<void>;
readonly lastSyncTime: number | undefined;
readonly onDidChangeLastSyncTime: Event<number>;
pull(): Promise<void>; pull(): Promise<void>;
sync(): Promise<void>; sync(): Promise<void>;
stop(): Promise<void>; stop(): Promise<void>;
@@ -297,7 +304,7 @@ export interface IUserDataSyncService {
export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService'); export const IUserDataAutoSyncService = createDecorator<IUserDataAutoSyncService>('IUserDataAutoSyncService');
export interface IUserDataAutoSyncService { export interface IUserDataAutoSyncService {
_serviceBrand: any; _serviceBrand: any;
readonly onError: Event<{ code: UserDataSyncErrorCode, source?: SyncSource }>; readonly onError: Event<UserDataSyncError>;
triggerAutoSync(): Promise<void>; triggerAutoSync(): Promise<void>;
} }
@@ -308,19 +315,6 @@ export interface IUserDataSyncUtilService {
resolveFormattingOptions(resource: URI): Promise<FormattingOptions>; 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 const IUserDataSyncLogService = createDecorator<IUserDataSyncLogService>('IUserDataSyncLogService');
export interface IUserDataSyncLogService extends ILogService { } export interface IUserDataSyncLogService extends ILogService { }
@@ -5,7 +5,7 @@
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc'; import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
import { Event } from 'vs/base/common/event'; import { 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 { URI } from 'vs/base/common/uri';
import { IStringDictionary } from 'vs/base/common/collections'; import { IStringDictionary } from 'vs/base/common/collections';
import { FormattingOptions } from 'vs/base/common/jsonFormatter'; import { FormattingOptions } from 'vs/base/common/jsonFormatter';
@@ -19,13 +19,14 @@ export class UserDataSyncChannel implements IServerChannel {
case 'onDidChangeStatus': return this.service.onDidChangeStatus; case 'onDidChangeStatus': return this.service.onDidChangeStatus;
case 'onDidChangeConflicts': return this.service.onDidChangeConflicts; case 'onDidChangeConflicts': return this.service.onDidChangeConflicts;
case 'onDidChangeLocal': return this.service.onDidChangeLocal; case 'onDidChangeLocal': return this.service.onDidChangeLocal;
case 'onDidChangeLastSyncTime': return this.service.onDidChangeLastSyncTime;
} }
throw new Error(`Event not found: ${event}`); throw new Error(`Event not found: ${event}`);
} }
call(context: any, command: string, args?: any): Promise<any> { call(context: any, command: string, args?: any): Promise<any> {
switch (command) { 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 'sync': return this.service.sync();
case 'accept': return this.service.accept(args[0], args[1]); case 'accept': return this.service.accept(args[0], args[1]);
case 'pull': return this.service.pull(); 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 { export class UserDataSycnUtilServiceChannel implements IServerChannel {
constructor(private readonly service: IUserDataSyncUtilService) { } constructor(private readonly service: IUserDataSyncUtilService) { }
@@ -21,6 +21,7 @@ type SyncErrorClassification = {
}; };
const SESSION_ID_KEY = 'sync.sessionId'; const SESSION_ID_KEY = 'sync.sessionId';
const LAST_SYNC_TIME_KEY = 'sync.lastSyncTime';
export class UserDataSyncService extends Disposable implements IUserDataSyncService { 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[]>()); private _onDidChangeConflicts: Emitter<SyncSource[]> = this._register(new Emitter<SyncSource[]>());
readonly onDidChangeConflicts: Event<SyncSource[]> = this._onDidChangeConflicts.event; 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 keybindingsSynchroniser: KeybindingsSynchroniser;
private readonly extensionsSynchroniser: ExtensionsSynchroniser; private readonly extensionsSynchroniser: ExtensionsSynchroniser;
private readonly globalStateSynchroniser: GlobalStateSynchroniser; 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._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)); 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> { async accept(source: SyncSource, content: string): Promise<void> {
await this.checkEnablement(); await this.checkEnablement();
const synchroniser = this.getSynchroniser(source); const synchroniser = this.getSynchroniser(source);
return synchroniser.accept(content); await synchroniser.accept(content);
} }
async getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null> { async getRemoteContent(source: SyncSource, preview: boolean): Promise<string | null> {
@@ -189,6 +196,7 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
async resetLocal(): Promise<void> { async resetLocal(): Promise<void> {
await this.checkEnablement(); await this.checkEnablement();
this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL); this.storageService.remove(SESSION_ID_KEY, StorageScope.GLOBAL);
this.storageService.remove(LAST_SYNC_TIME_KEY, StorageScope.GLOBAL);
for (const synchroniser of this.synchronisers) { for (const synchroniser of this.synchronisers) {
try { try {
synchroniser.resetLocal(); synchroniser.resetLocal();
@@ -227,9 +235,13 @@ export class UserDataSyncService extends Disposable implements IUserDataSyncServ
} }
private setStatus(status: SyncStatus): void { private setStatus(status: SyncStatus): void {
const oldStatus = this._status;
if (this._status !== status) { if (this._status !== status) {
this._status = status; this._status = status;
this._onDidChangeStatus.fire(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; 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 { private handleSyncError(e: Error, source: SyncSource): void {
if (e instanceof UserDataSyncStoreError) { if (e instanceof UserDataSyncStoreError) {
switch (e.code) { switch (e.code) {
@@ -4,12 +4,13 @@
*--------------------------------------------------------------------------------------------*/ *--------------------------------------------------------------------------------------------*/
import { Disposable, } from 'vs/base/common/lifecycle'; 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 { IRequestService, asText, isSuccess, asJson } from 'vs/platform/request/common/request';
import { joinPath } from 'vs/base/common/resources'; import { joinPath } from 'vs/base/common/resources';
import { CancellationToken } from 'vs/base/common/cancellation'; import { CancellationToken } from 'vs/base/common/cancellation';
import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request'; import { IHeaders, IRequestOptions, IRequestContext } from 'vs/base/parts/request/common/request';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService { export class UserDataSyncStoreService extends Disposable implements IUserDataSyncStoreService {
@@ -20,7 +21,7 @@ export class UserDataSyncStoreService extends Disposable implements IUserDataSyn
constructor( constructor(
@IConfigurationService configurationService: IConfigurationService, @IConfigurationService configurationService: IConfigurationService,
@IRequestService private readonly requestService: IRequestService, @IRequestService private readonly requestService: IRequestService,
@IUserDataAuthTokenService private readonly authTokenService: IUserDataAuthTokenService, @IAuthenticationTokenService private readonly authTokenService: IAuthenticationTokenService,
@IUserDataSyncLogService private readonly logService: IUserDataSyncLogService, @IUserDataSyncLogService private readonly logService: IUserDataSyncLogService,
) { ) {
super(); super();
@@ -3,10 +3,11 @@
* Licensed under the Source EULA. See License.txt in the project root for license information. * 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 { Event } from 'vs/base/common/event';
import { IElectronService } from 'vs/platform/electron/node/electron'; import { IElectronService } from 'vs/platform/electron/node/electron';
import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService'; import { UserDataAutoSyncService as BaseUserDataAutoSyncService } from 'vs/platform/userDataSync/common/userDataAutoSyncService';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class UserDataAutoSyncService extends BaseUserDataAutoSyncService { export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
@@ -15,7 +16,7 @@ export class UserDataAutoSyncService extends BaseUserDataAutoSyncService {
@IUserDataSyncService userDataSyncService: IUserDataSyncService, @IUserDataSyncService userDataSyncService: IUserDataSyncService,
@IElectronService electronService: IElectronService, @IElectronService electronService: IElectronService,
@IUserDataSyncLogService logService: IUserDataSyncLogService, @IUserDataSyncLogService logService: IUserDataSyncLogService,
@IUserDataAuthTokenService authTokenService: IUserDataAuthTokenService, @IAuthenticationTokenService authTokenService: IAuthenticationTokenService,
) { ) {
super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService); super(userDataSyncEnablementService, userDataSyncService, logService, authTokenService);
@@ -11,9 +11,9 @@ suite('ExtensionsMerge - No Conflicts', () => {
test('merge returns local extension if remote does not exist', async () => { test('merge returns local extension if remote does not exist', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, null, null, [], []); 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 () => { test('merge returns local extension if remote does not exist with ignored extensions', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, null, null, [], ['a']); 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 () => { test('merge returns local extension if remote does not exist with ignored extensions (ignore case)', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, null, null, [], ['A']); 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 () => { test('merge returns local extension if remote does not exist with skipped extensions', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const skippedExtension: ISyncExtension[] = [ const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, null, null, skippedExtension, []); 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 () => { test('merge returns local extension if remote does not exist with skipped and ignored extensions', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const skippedExtension: ISyncExtension[] = [ const skippedExtension: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, null, null, skippedExtension, ['a']); 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 () => { test('merge local and remote extensions when there is no base', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, null, [], []); 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.removed, []);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected); 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 () => { test('merge local and remote extensions when there is no base and with ignored extensions', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, null, [], ['a']); 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.removed, []);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected); 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 () => { test('merge local and remote extensions when remote is moved forwarded', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); 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.removed, [{ id: 'a', uuid: 'a' }, { id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null); 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[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { 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']); 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.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null); 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 () => { test('merge local and remote extensions when remote is moved forwarded with skipped extensions', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const skippedExtensions: ISyncExtension[] = [ const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); 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.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null); 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 () => { test('merge local and remote extensions when remote is moved forwarded with skipped and ignored extensions', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const skippedExtensions: ISyncExtension[] = [ const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['b']); 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.removed, [{ id: 'd', uuid: 'd' }]);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.equal(actual.remote, null); 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 () => { test('merge local and remote extensions when local is moved forwarded', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { 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, [], []); 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 () => { test('merge local and remote extensions when local is moved forwarded with ignored settings', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']); const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['b']);
@@ -288,30 +334,30 @@ suite('ExtensionsMerge - No Conflicts', () => {
assert.deepEqual(actual.removed, []); assert.deepEqual(actual.removed, []);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, [ 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 () => { test('merge local and remote extensions when local is moved forwarded with skipped extensions', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const skippedExtensions: ISyncExtension[] = [ const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); 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 () => { test('merge local and remote extensions when local is moved forwarded with skipped and ignored extensions', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const skippedExtensions: ISyncExtension[] = [ const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['c']); 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 () => { test('merge local and remote extensions when both moved forwarded', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], []); 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.removed, [{ id: 'a', uuid: 'a' }]);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected); 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 () => { test('merge local and remote extensions when both moved forwarded with ignored extensions', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, [], ['a', 'e']); 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 () => { test('merge local and remote extensions when both moved forwarded with skipped extensions', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const skippedExtensions: ISyncExtension[] = [ const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, []); 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.removed, []);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected); 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 () => { test('merge local and remote extensions when both moved forwarded with skipped and ignoredextensions', async () => {
const baseExtensions: ISyncExtension[] = [ const baseExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const skippedExtensions: ISyncExtension[] = [ const skippedExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
]; ];
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'e', uuid: 'e' }, enabled: true }, { identifier: { id: 'e', uuid: 'e' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, baseExtensions, skippedExtensions, ['e']); 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 () => { test('merge when remote extension has no uuid and different extension id case', async () => {
const localExtensions: ISyncExtension[] = [ const localExtensions: ISyncExtension[] = [
{ identifier: { id: 'a', uuid: 'a' }, enabled: true }, { identifier: { id: 'a', uuid: 'a' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const remoteExtensions: ISyncExtension[] = [ const remoteExtensions: ISyncExtension[] = [
{ identifier: { id: 'A' }, enabled: true }, { identifier: { id: 'A' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
]; ];
const expected: ISyncExtension[] = [ const expected: ISyncExtension[] = [
{ identifier: { id: 'A' }, enabled: true }, { identifier: { id: 'A', uuid: 'a' } },
{ identifier: { id: 'd', uuid: 'd' }, enabled: true }, { identifier: { id: 'd', uuid: 'd' } },
{ identifier: { id: 'b', uuid: 'b' }, enabled: true }, { identifier: { id: 'b', uuid: 'b' } },
{ identifier: { id: 'c', uuid: 'c' }, enabled: true }, { identifier: { id: 'c', uuid: 'c' } },
]; ];
const actual = merge(localExtensions, remoteExtensions, null, [], []); 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.removed, []);
assert.deepEqual(actual.updated, []); assert.deepEqual(actual.updated, []);
assert.deepEqual(actual.remote, expected); assert.deepEqual(actual.remote, expected);
@@ -6,7 +6,7 @@
import { IRequestService } from 'vs/platform/request/common/request'; import { IRequestService } from 'vs/platform/request/common/request';
import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request'; import { IRequestOptions, IRequestContext, IHeaders } from 'vs/base/parts/request/common/request';
import { CancellationToken } from 'vs/base/common/cancellation'; 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 { bufferToStream, VSBuffer } from 'vs/base/common/buffer';
import { generateUuid } from 'vs/base/common/uuid'; import { generateUuid } from 'vs/base/common/uuid';
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService'; 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 { Disposable } from 'vs/base/common/lifecycle';
import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync'; import { SettingsSynchroniser } from 'vs/platform/userDataSync/common/settingsSync';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
import { IAuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
export class UserDataSyncClient extends Disposable { export class UserDataSyncClient extends Disposable {
@@ -76,7 +77,7 @@ export class UserDataSyncClient extends Disposable {
this.instantiationService.stub(IConfigurationService, configurationService); this.instantiationService.stub(IConfigurationService, configurationService);
this.instantiationService.stub(IRequestService, this.testServer); this.instantiationService.stub(IRequestService, this.testServer);
this.instantiationService.stub(IUserDataAuthTokenService, <Partial<IUserDataAuthTokenService>>{ this.instantiationService.stub(IAuthenticationTokenService, <Partial<IAuthenticationTokenService>>{
onDidChangeToken: new Emitter<string | undefined>().event, onDidChangeToken: new Emitter<string | undefined>().event,
async getToken() { return 'token'; } 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 { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { VSBuffer } from 'vs/base/common/buffer'; import { VSBuffer } from 'vs/base/common/buffer';
suite('UserDataSyncService', () => { suite.skip('UserDataSyncService', () => { // {{SQL CARBON EDIT}} skip failing tests
const disposableStore = new DisposableStore(); const disposableStore = new DisposableStore();
+40 -6
View File
@@ -2897,6 +2897,34 @@ declare module 'vscode' {
constructor(range: Range, newText: string); 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 * A workspace edit is a collection of textual and files changes for
* multiple resources and documents. * multiple resources and documents.
@@ -2916,8 +2944,9 @@ declare module 'vscode' {
* @param uri A resource identifier. * @param uri A resource identifier.
* @param range A range. * @param range A range.
* @param newText A string. * @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. * Insert the given text at the given position.
@@ -2925,16 +2954,18 @@ declare module 'vscode' {
* @param uri A resource identifier. * @param uri A resource identifier.
* @param position A position. * @param position A position.
* @param newText A string. * @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. * Delete the text at the given range.
* *
* @param uri A resource identifier. * @param uri A resource identifier.
* @param range A range. * @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. * Check if a text edit for a resource exists.
@@ -2966,15 +2997,17 @@ declare module 'vscode' {
* @param uri Uri of the new file.. * @param uri Uri of the new file..
* @param options Defines if an existing file should be overwritten or be * @param options Defines if an existing file should be overwritten or be
* ignored. When overwrite and ignoreIfExists are both set overwrite wins. * 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. * Delete a file or folder.
* *
* @param uri The uri of the file that is to be deleted. * @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. * Rename a file or folder.
@@ -2983,8 +3016,9 @@ declare module 'vscode' {
* @param newUri The new location. * @param newUri The new location.
* @param options Defines if existing files should be overwritten or be * @param options Defines if existing files should be overwritten or be
* ignored. When overwrite and ignoreIfExists are both set overwrite wins. * 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 //#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 //#region Diagnostic links https://github.com/microsoft/vscode/issues/11847
export interface Diagnostic { 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 { extHostCustomer } from 'vs/workbench/api/common/extHostCustomers';
import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol'; import { ExtHostContext, FileSystemEvents, IExtHostContext } from '../common/extHost.protocol';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; 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 { localize } from 'vs/nls';
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry'; import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { ILogService } from 'vs/platform/log/common/log'; 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 { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
import { URI } from 'vs/base/common/uri';
import { IWaitUntil } from 'vs/base/common/event';
@extHostCustomer @extHostCustomer
export class MainThreadFileSystemEventService { export class MainThreadFileSystemEventService {
@@ -65,43 +62,11 @@ export class MainThreadFileSystemEventService {
// BEFORE file operation // BEFORE file operation
const messages = new Map<FileOperation, string>(); workingCopyFileService.addFileOperationParticipant({
messages.set(FileOperation.CREATE, localize('msg-create', "Running 'File Create' participants...")); participate: (target, source, operation, progress, timeout, token) => {
messages.set(FileOperation.DELETE, localize('msg-delete', "Running 'File Delete' participants...")); return proxy.$onWillRunFileOperation(operation, target, source, timeout, token);
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
} }
});
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 // AFTER file operation
this._listener.add(textFileService.onDidCreateTextFile(e => proxy.$onDidRunFileOperation(FileOperation.CREATE, e.resource, undefined))); 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)); CommandsRegistry.registerCommand(RemoveFromRecentlyOpenedAPICommand.ID, adjustHandler(RemoveFromRecentlyOpenedAPICommand.execute));
export interface OpenIssueReporterArgs {
readonly extensionId: string;
readonly issueTitle?: string;
readonly issueBody?: string;
}
export class OpenIssueReporter { export class OpenIssueReporter {
public static readonly ID = 'vscode.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 { ICommandHandlerDescription } from 'vs/platform/commands/common/commands';
import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands'; import { ExtHostCommands } from 'vs/workbench/api/common/extHostCommands';
import { CustomCodeAction } from 'vs/workbench/api/common/extHostLanguageFeatures'; 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 { EditorGroupLayout } from 'vs/workbench/services/editor/common/editorGroupsService';
import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
@@ -364,7 +364,7 @@ export class ExtHostApiCommands {
this._register(OpenIssueReporter.ID, adjustHandler(OpenIssueReporter.execute), { this._register(OpenIssueReporter.ID, adjustHandler(OpenIssueReporter.execute), {
description: 'Opens the issue reporter with the provided extension id as the selected source', description: 'Opens the issue reporter with the provided extension id as the selected source',
args: [ 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 { VSBuffer } from 'vs/base/common/buffer';
import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento'; import { ExtensionMemento } from 'vs/workbench/api/common/extHostMemento';
import { RemoteAuthorityResolverError } from 'vs/workbench/api/common/extHostTypes'; 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 { IInstantiationService, createDecorator } from 'vs/platform/instantiation/common/instantiation';
import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService'; import { IExtHostInitDataService } from 'vs/workbench/api/common/extHostInitDataService';
import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths'; import { IExtensionStoragePaths } from 'vs/workbench/api/common/extHostStoragePaths';
@@ -641,7 +641,14 @@ export abstract class AbstractExtHostExtensionService implements ExtHostExtensio
const resolver = this._resolvers[authorityPrefix]; const resolver = this._resolvers[authorityPrefix];
if (!resolver) { 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 { try {
+8 -8
View File
@@ -576,14 +576,14 @@ export interface IFileOperation {
from?: URI; from?: URI;
to?: URI; to?: URI;
options?: IFileOperationOptions; options?: IFileOperationOptions;
metadata?: vscode.WorkspaceEditMetadata; metadata?: vscode.WorkspaceEditEntryMetadata;
} }
export interface IFileTextEdit { export interface IFileTextEdit {
_type: 2; _type: 2;
uri: URI; uri: URI;
edit: TextEdit; edit: TextEdit;
metadata?: vscode.WorkspaceEditMetadata; metadata?: vscode.WorkspaceEditEntryMetadata;
} }
@es5ClassCompat @es5ClassCompat
@@ -591,27 +591,27 @@ export class WorkspaceEdit implements vscode.WorkspaceEdit {
private _edits = new Array<IFileOperation | IFileTextEdit>(); 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 }); 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 }); 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 }); 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 }); 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); 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); this.replace(resource, range, '', metadata);
} }
@@ -9,7 +9,7 @@ import * as nls from 'vs/nls';
import { Registry } from 'vs/platform/registry/common/platform'; import { Registry } from 'vs/platform/registry/common/platform';
import { Action } from 'vs/base/common/actions'; import { Action } from 'vs/base/common/actions';
import { SyncActionDescriptor, MenuId, MenuRegistry } from 'vs/platform/actions/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 { IConfigurationService, ConfigurationTarget } from 'vs/platform/configuration/common/configuration';
import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService'; import { IWorkbenchLayoutService, Parts, Position } from 'vs/workbench/services/layout/browser/layoutService';
import { IEditorGroupsService, GroupOrientation } from 'vs/workbench/services/editor/common/editorGroupsService'; 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 { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { SideBarVisibleContext } from 'vs/workbench/common/viewlet'; import { SideBarVisibleContext } from 'vs/workbench/common/viewlet';
import { IEnvironmentService } from 'vs/platform/environment/common/environment'; 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"); const viewCategory = nls.localize('view', "View");
// --- Close Side Bar // --- Close Side Bar
@@ -482,6 +483,42 @@ MenuRegistry.appendMenuItem(MenuId.MenubarAppearanceMenu, {
order: 0 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 // --- Resize View
export abstract class BaseResizeViewAction extends Action { export abstract class BaseResizeViewAction extends Action {
+1
View File
@@ -1180,6 +1180,7 @@ export abstract class Layout extends Disposable implements IWorkbenchLayoutServi
} else { } else {
this.setEditorHidden(false); 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.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 { 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 { 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 { 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 { Dimension, addClass, removeNode } from 'vs/base/browser/dom';
import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage'; import { IStorageService, StorageScope, IWorkspaceStorageChangeEvent } from 'vs/platform/storage/common/storage';
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions'; import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
import { URI, UriComponents } from 'vs/base/common/uri'; import { URI, UriComponents } from 'vs/base/common/uri';
import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions'; import { ToggleCompositePinnedAction, ICompositeBarColors, ActivityAction, ICompositeActivity } from 'vs/workbench/browser/parts/compositeBarActions';
import { ViewletDescriptor } from 'vs/workbench/browser/viewlet'; 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 { IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
import { IViewlet } from 'vs/workbench/common/viewlet'; import { IViewlet } from 'vs/workbench/common/viewlet';
import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types'; import { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
@@ -128,6 +128,11 @@ export class ActivitybarPart extends Part implements IActivityBarService {
getContextMenuActionsForComposite: () => [], getContextMenuActionsForComposite: () => [],
getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(), getDefaultCompositeId: () => this.viewletService.getDefaultViewletId(),
hidePart: () => this.layoutService.setSideBarHidden(true), 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, compositeSize: 50,
colors: (theme: ITheme) => this.getActivitybarItemColors(theme), colors: (theme: ITheme) => this.getActivitybarItemColors(theme),
overflowActionSize: ActivitybarPart.ACTION_HEIGHT 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 { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { ITheme } from 'vs/platform/theme/common/themeService'; import { ITheme } from 'vs/platform/theme/common/themeService';
import { Emitter } from 'vs/base/common/event'; 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 { export interface ICompositeBarItem {
id: string; id: string;
@@ -29,12 +33,120 @@ export interface ICompositeBarItem {
visible: boolean; 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 { export interface ICompositeBarOptions {
readonly icon: boolean; readonly icon: boolean;
readonly orientation: ActionsOrientation; readonly orientation: ActionsOrientation;
readonly colors: (theme: ITheme) => ICompositeBarColors; readonly colors: (theme: ITheme) => ICompositeBarColors;
readonly compositeSize: number; readonly compositeSize: number;
readonly overflowActionSize: number; readonly overflowActionSize: number;
readonly dndHandler: ICompositeDragAndDrop;
getActivityAction: (compositeId: string) => ActivityAction; getActivityAction: (compositeId: string) => ActivityAction;
getCompositePinnedAction: (compositeId: string) => Action; getCompositePinnedAction: (compositeId: string) => Action;
@@ -58,7 +170,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
private visibleComposites: string[]; private visibleComposites: string[];
private compositeSizeInBar: Map<string, number>; private compositeSizeInBar: Map<string, number>;
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier>; private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>()); private readonly _onDidChange: Emitter<void> = this._register(new Emitter<void>());
readonly onDidChange = this._onDidChange.event; readonly onDidChange = this._onDidChange.event;
@@ -107,6 +219,7 @@ export class CompositeBar extends Widget implements ICompositeBar {
() => this.getContextMenuActions() as Action[], () => this.getContextMenuActions() as Action[],
this.options.colors, this.options.colors,
this.options.icon, this.options.icon,
this.options.dndHandler,
this 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; return actionBarDiv;
@@ -20,6 +20,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
import { Emitter } from 'vs/base/common/event'; import { Emitter } from 'vs/base/common/event';
import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd'; import { DragAndDropObserver, LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
import { Color } from 'vs/base/common/color'; 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 { export interface ICompositeActivity {
badge: IBadge; badge: IBadge;
@@ -458,7 +460,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
private static manageExtensionAction: ManageExtensionAction; private static manageExtensionAction: ManageExtensionAction;
private compositeActivity: IActivity | undefined; private compositeActivity: IActivity | undefined;
private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier>; private compositeTransfer: LocalSelectionTransfer<DraggedCompositeIdentifier | DraggedViewIdentifier>;
constructor( constructor(
private compositeActivityAction: ActivityAction, private compositeActivityAction: ActivityAction,
@@ -467,6 +469,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
private contextMenuActionsProvider: () => ReadonlyArray<Action>, private contextMenuActionsProvider: () => ReadonlyArray<Action>,
colors: (theme: ITheme) => ICompositeBarColors, colors: (theme: ITheme) => ICompositeBarColors,
icon: boolean, icon: boolean,
private dndHandler: ICompositeDragAndDrop,
private compositeBar: ICompositeBar, private compositeBar: ICompositeBar,
@IContextMenuService private readonly contextMenuService: IContextMenuService, @IContextMenuService private readonly contextMenuService: IContextMenuService,
@IKeybindingService private readonly keybindingService: IKeybindingService, @IKeybindingService private readonly keybindingService: IKeybindingService,
@@ -475,7 +478,7 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
) { ) {
super(compositeActivityAction, { draggable: true, colors, icon }, themeService); super(compositeActivityAction, { draggable: true, colors, icon }, themeService);
this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier>(); this.compositeTransfer = LocalSelectionTransfer.getInstance<DraggedCompositeIdentifier | DraggedViewIdentifier>();
if (!CompositeActionViewItem.manageExtensionAction) { if (!CompositeActionViewItem.manageExtensionAction) {
CompositeActionViewItem.manageExtensionAction = instantiationService.createInstance(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 => { onDragLeave: e => {
if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) { if (this.compositeTransfer.hasData(DraggedCompositeIdentifier.prototype)) {
this.updateFromDragging(container, false); this.updateFromDragging(container, false);
@@ -571,16 +599,27 @@ export class CompositeActionViewItem extends ActivityActionViewItem {
this.updateFromDragging(container, false); this.updateFromDragging(container, false);
this.compositeTransfer.clearData(DraggedCompositeIdentifier.prototype); 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 // Activate on drag over to reveal targets
[this.badge, this.label].forEach(b => this._register(new DelayedDragHandler(b, () => { [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(); this.getAction().run();
} }
}))); })));
@@ -22,7 +22,7 @@ import { ClosePanelAction, PanelActivityAction, ToggleMaximizedPanelAction, Togg
import { IThemeService, registerThemingParticipant, ITheme, ICssStyleCollector } from 'vs/platform/theme/common/themeService'; 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 { 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 { 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 { ToggleCompositePinnedAction } from 'vs/workbench/browser/parts/compositeBarActions';
import { IBadge } from 'vs/workbench/services/activity/common/activity'; import { IBadge } from 'vs/workbench/services/activity/common/activity';
import { INotificationService } from 'vs/platform/notification/common/notification'; 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 { isUndefinedOrNull, assertIsDefined } from 'vs/base/common/types';
import { registerSingleton } from 'vs/platform/instantiation/common/extensions'; import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
import { IExtensionService } from 'vs/workbench/services/extensions/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 { MenuId } from 'vs/platform/actions/common/actions';
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions'; 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[], getContextMenuActionsForComposite: (compositeId: string) => this.getContextMenuActionsForComposite(compositeId) as Action[],
getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(), getDefaultCompositeId: () => this.panelRegistry.getDefaultPanelId(),
hidePart: () => this.layoutService.setPanelHidden(true), 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, compositeSize: 0,
overflowActionSize: 44, overflowActionSize: 44,
colors: (theme: ITheme) => ({ colors: (theme: ITheme) => ({
@@ -397,7 +402,17 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
getPanels(): readonly PanelDescriptor[] { getPanels(): readonly PanelDescriptor[] {
return this.panelRegistry.getPanels() 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[] { getPinnedPanels(): readonly PanelDescriptor[] {
@@ -42,6 +42,7 @@ import { parseLinkedText } from 'vs/base/common/linkedText';
import { IOpenerService } from 'vs/platform/opener/common/opener'; import { IOpenerService } from 'vs/platform/opener/common/opener';
import { Button } from 'vs/base/browser/ui/button/button'; import { Button } from 'vs/base/browser/ui/button/button';
import { Link } from 'vs/platform/opener/browser/link'; import { Link } from 'vs/platform/opener/browser/link';
import { LocalSelectionTransfer } from 'vs/workbench/browser/dnd';
export interface IPaneColors extends IColorMapping { export interface IPaneColors extends IColorMapping {
dropBackground?: ColorIdentifier; dropBackground?: ColorIdentifier;
@@ -57,6 +58,15 @@ export interface IViewPaneOptions extends IPaneOptions {
titleMenuId?: MenuId; titleMenuId?: MenuId;
} }
export class DraggedViewIdentifier {
constructor(private _viewId: string) { }
get id(): string {
return this._viewId;
}
}
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry); const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
interface IItem { interface IItem {
@@ -444,6 +454,8 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
private paneItems: IViewPaneItem[] = []; private paneItems: IViewPaneItem[] = [];
private paneview?: PaneView; private paneview?: PaneView;
private static viewTransfer = LocalSelectionTransfer.getInstance<DraggedViewIdentifier>();
private visible: boolean = false; private visible: boolean = false;
private areExtensionsReady: boolean = false; private areExtensionsReady: boolean = false;
@@ -874,6 +886,22 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
this.paneItems.splice(index, 0, paneItem); this.paneItems.splice(index, 0, paneItem);
assertIsDefined(this.paneview).addPane(pane, size, index); 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 { removePanes(panes: ViewPane[]): void {
@@ -952,7 +980,7 @@ export class ViewPaneContainer extends Component implements IViewPaneContainer {
} }
if (!this.areExtensionsReady) { if (!this.areExtensionsReady) {
if (this.visibleViewsCountFromCache === undefined) { if (this.visibleViewsCountFromCache === undefined) {
return false; return true;
} }
// Check in cache so that view do not jump. See #29609 // Check in cache so that view do not jump. See #29609
return this.visibleViewsCountFromCache === 1; return this.visibleViewsCountFromCache === 1;
@@ -591,7 +591,7 @@ export class ViewsService extends Disposable implements IViewsService {
} }
run(accessor: ServicesAccessor): any { run(accessor: ServicesAccessor): any {
accessor.get(IViewDescriptorService).moveViewToLocation(viewDescriptor, newLocation); 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 { IPathData } from 'vs/platform/windows/common/windows';
import { coalesce, firstOrDefault } from 'vs/base/common/arrays'; import { coalesce, firstOrDefault } from 'vs/base/common/arrays';
import { ITextFileSaveOptions, ITextFileService } from 'vs/workbench/services/textfile/common/textfiles'; 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 { isEqual, dirname } from 'vs/base/common/resources';
import { IPanel } from 'vs/workbench/common/panel'; import { IPanel } from 'vs/workbench/common/panel';
import { IRange } from 'vs/editor/common/core/range'; import { IRange } from 'vs/editor/common/core/range';
@@ -345,6 +345,11 @@ export interface IRevertOptions {
readonly soft?: boolean; readonly soft?: boolean;
} }
export interface IMoveResult {
editor: IEditorInput | IResourceEditor;
options?: IEditorOptions;
}
export interface IEditorInput extends IDisposable { export interface IEditorInput extends IDisposable {
/** /**
@@ -446,6 +451,16 @@ export interface IEditorInput extends IDisposable {
*/ */
revert(group: GroupIdentifier, options?: IRevertOptions): Promise<boolean>; 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. * Returns if the other object matches this input.
*/ */
@@ -546,6 +561,10 @@ export abstract class EditorInput extends Disposable implements IEditorInput {
return true; 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. * 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. * Forces this file input to open as binary instead of text.
*/ */
setForceOpenAsBinary(): void; 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) { if (this.selectionRevealType === TextEditorSelectionRevealType.NearTop) {
editor.revealRangeNearTop(range, scrollType); editor.revealRangeNearTop(range, scrollType);
} else if (this.selectionRevealType === TextEditorSelectionRevealType.NearTopIfOutsideViewport) {
editor.revealRangeNearTopIfOutsideViewport(range, scrollType);
} else if (this.selectionRevealType === TextEditorSelectionRevealType.CenterIfOutsideViewport) { } else if (this.selectionRevealType === TextEditorSelectionRevealType.CenterIfOutsideViewport) {
editor.revealRangeInCenterIfOutsideViewport(range, scrollType); editor.revealRangeInCenterIfOutsideViewport(range, scrollType);
} else { } else {

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