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