Archived
Merge from vscode 8df646d3c5477b02737fc10343fa7cf0cc3f606b
This commit is contained in:
@@ -103,6 +103,7 @@ export class AzureActiveDirectoryService {
|
||||
|
||||
await Promise.all(refreshes);
|
||||
} catch (e) {
|
||||
Logger.info('Failed to initialize stored data');
|
||||
await this.clearSessions();
|
||||
}
|
||||
}
|
||||
@@ -170,6 +171,7 @@ export class AzureActiveDirectoryService {
|
||||
if (this._tokens.length) {
|
||||
// Log out all
|
||||
removedIds = this._tokens.map(token => token.sessionId);
|
||||
Logger.info('No tokens in memory, clearing keychain data');
|
||||
await this.clearSessions();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
@font-face {
|
||||
font-family: "codicon";
|
||||
src: url("./codicon.ttf?fb4c14f317e1decb0289895ecc9356f0") format("truetype");
|
||||
src: url("./codicon.ttf?3d9ee7d873425ff0bc441f48a1de0c54") format("truetype");
|
||||
}
|
||||
|
||||
.codicon[class*='codicon-'] {
|
||||
@@ -420,4 +420,7 @@
|
||||
.codicon-bell-dot:before { content: "\f102" }
|
||||
.codicon-debug-alt-2:before { content: "\f103" }
|
||||
.codicon-debug-alt:before { content: "\f104" }
|
||||
.codicon-run-all:before { content: "\f105" }
|
||||
.codicon-debug-console:before { content: "\f105" }
|
||||
.codicon-library:before { content: "\f106" }
|
||||
.codicon-output:before { content: "\f107" }
|
||||
.codicon-run-all:before { content: "\f108" }
|
||||
|
||||
Binary file not shown.
@@ -275,6 +275,12 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||
this.layout();
|
||||
}
|
||||
|
||||
updateOptions(options: IListViewOptions<T>) {
|
||||
if (options.additionalScrollHeight !== undefined) {
|
||||
this.additionalScrollHeight = options.additionalScrollHeight;
|
||||
}
|
||||
}
|
||||
|
||||
triggerScrollFromMouseWheelEvent(browserEvent: IMouseWheelEvent) {
|
||||
this.scrollableElement.triggerScrollFromMouseWheelEvent(browserEvent);
|
||||
}
|
||||
|
||||
@@ -854,6 +854,7 @@ export interface IListOptions<T> {
|
||||
readonly mouseSupport?: boolean;
|
||||
readonly horizontalScrolling?: boolean;
|
||||
readonly ariaProvider?: IAriaProvider<T>;
|
||||
readonly additionalScrollHeight?: number;
|
||||
}
|
||||
|
||||
export interface IListStyles {
|
||||
@@ -1110,6 +1111,7 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||
export interface IListOptionsUpdate {
|
||||
readonly enableKeyboardNavigation?: boolean;
|
||||
readonly automaticKeyboardNavigation?: boolean;
|
||||
readonly additionalScrollHeight?: number;
|
||||
}
|
||||
|
||||
export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
@@ -1289,6 +1291,10 @@ export class List<T> implements ISpliceable<T>, IDisposable {
|
||||
if (this.typeLabelController) {
|
||||
this.typeLabelController.updateOptions(this._options);
|
||||
}
|
||||
|
||||
if (optionsUpdate.additionalScrollHeight !== undefined) {
|
||||
this.view.updateOptions(optionsUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
get options(): IListOptions<T> {
|
||||
|
||||
@@ -110,7 +110,7 @@ abstract class ViewItem<TLayoutContext> {
|
||||
get snap(): boolean { return !!this.view.snap; }
|
||||
|
||||
set enabled(enabled: boolean) {
|
||||
this.container.style.pointerEvents = enabled ? null : 'none';
|
||||
this.container.style.pointerEvents = enabled ? '' : 'none';
|
||||
}
|
||||
|
||||
constructor(
|
||||
|
||||
@@ -791,7 +791,7 @@ export class AsyncDataTree<TInput, T, TFilterData = void> implements IDisposable
|
||||
|
||||
this.refreshPromises.set(node, result);
|
||||
|
||||
return result.finally(() => this.refreshPromises.delete(node));
|
||||
return result.finally(() => { this.refreshPromises.delete(node); });
|
||||
}
|
||||
|
||||
private _onDidChangeCollapseState({ node, deep }: ICollapseStateChangeEvent<IAsyncDataTreeNode<TInput, T> | null, any>): void {
|
||||
|
||||
@@ -28,11 +28,17 @@ export class HistoryNavigator<T> implements INavigator<T> {
|
||||
}
|
||||
|
||||
public next(): T | null {
|
||||
return this._navigator.next();
|
||||
if (this._currentPosition() !== this._elements.length - 1) {
|
||||
return this._navigator.next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public previous(): T | null {
|
||||
return this._navigator.previous();
|
||||
if (this._currentPosition() !== 0) {
|
||||
return this._navigator.previous();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public current(): T | null {
|
||||
@@ -73,6 +79,15 @@ export class HistoryNavigator<T> implements INavigator<T> {
|
||||
}
|
||||
}
|
||||
|
||||
private _currentPosition(): number {
|
||||
const currentElement = this._navigator.current();
|
||||
if (!currentElement) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return this._elements.indexOf(currentElement);
|
||||
}
|
||||
|
||||
private _initialize(history: readonly T[]): void {
|
||||
this._history = new Set();
|
||||
for (const entry of history) {
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import { URI, originalFSPath as uriOriginalFSPath } from 'vs/base/common/uri';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { equalsIgnoreCase } from 'vs/base/common/strings';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { isLinux, isWindows } from 'vs/base/common/platform';
|
||||
@@ -13,7 +13,28 @@ import { CharCode } from 'vs/base/common/charCode';
|
||||
import { ParsedExpression, IExpression, parse } from 'vs/base/common/glob';
|
||||
import { TernarySearchTree } from 'vs/base/common/map';
|
||||
|
||||
export const originalFSPath = uriOriginalFSPath;
|
||||
export function originalFSPath(uri: URI): string {
|
||||
let value: string;
|
||||
const uriPath = uri.path;
|
||||
if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
value = `//${uri.authority}${uriPath}`;
|
||||
} else if (
|
||||
isWindows
|
||||
&& uriPath.charCodeAt(0) === CharCode.Slash
|
||||
&& extpath.isWindowsDriveLetter(uriPath.charCodeAt(1))
|
||||
&& uriPath.charCodeAt(2) === CharCode.Colon
|
||||
) {
|
||||
value = uriPath.substr(1);
|
||||
} else {
|
||||
// other path
|
||||
value = uriPath;
|
||||
}
|
||||
if (isWindows) {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a key from a resource URI to be used to resource comparison and for resource maps.
|
||||
@@ -24,7 +45,7 @@ export function getComparisonKey(resource: URI, caseInsensitivePath = hasToIgnor
|
||||
if (caseInsensitivePath) {
|
||||
path = path.toLowerCase();
|
||||
}
|
||||
return `${resource.scheme}://${resource.authority.toLowerCase()}/${path}?${resource.query}`;
|
||||
return resource.with({ authority: resource.authority.toLowerCase(), path: path, fragment: null }).toString();
|
||||
}
|
||||
|
||||
export function hasToIgnoreCase(resource: URI | undefined): boolean {
|
||||
@@ -123,7 +144,15 @@ export function dirname(resource: URI): URI {
|
||||
* @returns The resulting URI.
|
||||
*/
|
||||
export function joinPath(resource: URI, ...pathFragment: string[]): URI {
|
||||
return URI.joinPaths(resource, ...pathFragment);
|
||||
let joinedPath: string;
|
||||
if (resource.scheme === 'file') {
|
||||
joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path;
|
||||
} else {
|
||||
joinedPath = paths.posix.join(resource.path || '/', ...pathFragment);
|
||||
}
|
||||
return resource.with({
|
||||
path: joinedPath
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
+19
-41
@@ -6,7 +6,6 @@
|
||||
import { isWindows } from 'vs/base/common/platform';
|
||||
import { CharCode } from 'vs/base/common/charCode';
|
||||
import * as paths from 'vs/base/common/path';
|
||||
import * as extpath from 'vs/base/common/extpath';
|
||||
|
||||
const _schemePattern = /^\w[\w\d+.-]*$/;
|
||||
const _singleSlashStart = /^\//;
|
||||
@@ -206,7 +205,7 @@ export class URI implements UriComponents {
|
||||
// if (this.scheme !== 'file') {
|
||||
// console.warn(`[UriError] calling fsPath with scheme ${this.scheme}`);
|
||||
// }
|
||||
return _makeFsPath(this);
|
||||
return _makeFsPath(this, false);
|
||||
}
|
||||
|
||||
// ---- modify to new -------------------------
|
||||
@@ -340,20 +339,21 @@ export class URI implements UriComponents {
|
||||
/**
|
||||
* Join a URI path with path fragments and normalizes the resulting path.
|
||||
*
|
||||
* @param resource The input URI.
|
||||
* @param uri The input URI.
|
||||
* @param pathFragment The path fragment to add to the URI path.
|
||||
* @returns The resulting URI.
|
||||
*/
|
||||
static joinPaths(resource: URI, ...pathFragment: string[]): URI {
|
||||
let joinedPath: string;
|
||||
if (resource.scheme === 'file') {
|
||||
joinedPath = URI.file(paths.join(originalFSPath(resource), ...pathFragment)).path;
|
||||
} else {
|
||||
joinedPath = paths.posix.join(resource.path || '/', ...pathFragment);
|
||||
static joinPath(uri: URI, ...pathFragment: string[]): URI {
|
||||
if (!uri.path) {
|
||||
throw new Error(`[UriError]: cannot call joinPaths on URI without path`);
|
||||
}
|
||||
return resource.with({
|
||||
path: joinedPath
|
||||
});
|
||||
let newPath: string;
|
||||
if (isWindows && uri.scheme === 'file') {
|
||||
newPath = URI.file(paths.win32.join(_makeFsPath(uri, true), ...pathFragment)).path;
|
||||
} else {
|
||||
newPath = paths.posix.join(uri.path, ...pathFragment);
|
||||
}
|
||||
return uri.with({ path: newPath });
|
||||
}
|
||||
|
||||
// ---- printing/externalize ---------------------------
|
||||
@@ -421,7 +421,7 @@ class _URI extends URI {
|
||||
|
||||
get fsPath(): string {
|
||||
if (!this._fsPath) {
|
||||
this._fsPath = _makeFsPath(this);
|
||||
this._fsPath = _makeFsPath(this, false);
|
||||
}
|
||||
return this._fsPath;
|
||||
}
|
||||
@@ -577,7 +577,7 @@ function encodeURIComponentMinimal(path: string): string {
|
||||
/**
|
||||
* Compute `fsPath` for the given uri
|
||||
*/
|
||||
function _makeFsPath(uri: URI): string {
|
||||
function _makeFsPath(uri: URI, keepDriveLetterCasing: boolean): string {
|
||||
|
||||
let value: string;
|
||||
if (uri.authority && uri.path.length > 1 && uri.scheme === 'file') {
|
||||
@@ -589,7 +589,11 @@ function _makeFsPath(uri: URI): string {
|
||||
&& uri.path.charCodeAt(2) === CharCode.Colon
|
||||
) {
|
||||
// windows drive letter: file:///c:/far/boo
|
||||
value = uri.path[1].toLowerCase() + uri.path.substr(2);
|
||||
if (!keepDriveLetterCasing) {
|
||||
value = uri.path[1].toLowerCase() + uri.path.substr(2);
|
||||
} else {
|
||||
value = uri.path.substr(1, 2);
|
||||
}
|
||||
} else {
|
||||
// other path
|
||||
value = uri.path;
|
||||
@@ -695,29 +699,3 @@ function percentDecode(str: string): string {
|
||||
}
|
||||
return str.replace(_rEncodedAsHex, (match) => decodeURIComponentGraceful(match));
|
||||
}
|
||||
|
||||
|
||||
// --- utils
|
||||
|
||||
export function originalFSPath(uri: URI): string {
|
||||
let value: string;
|
||||
const uriPath = uri.path;
|
||||
if (uri.authority && uriPath.length > 1 && uri.scheme === 'file') {
|
||||
// unc path: file://shares/c$/far/boo
|
||||
value = `//${uri.authority}${uriPath}`;
|
||||
} else if (
|
||||
isWindows
|
||||
&& uriPath.charCodeAt(0) === CharCode.Slash
|
||||
&& extpath.isWindowsDriveLetter(uriPath.charCodeAt(1))
|
||||
&& uriPath.charCodeAt(2) === CharCode.Colon
|
||||
) {
|
||||
value = uriPath.substr(1);
|
||||
} else {
|
||||
// other path
|
||||
value = uriPath;
|
||||
}
|
||||
if (isWindows) {
|
||||
value = value.replace(/\//g, '\\');
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
@@ -526,7 +526,7 @@ export class ChannelClient implements IChannelClient, IDisposable {
|
||||
this.activeRequests.add(disposable);
|
||||
});
|
||||
|
||||
return result.finally(() => this.activeRequests.delete(disposable));
|
||||
return result.finally(() => { this.activeRequests.delete(disposable); });
|
||||
}
|
||||
|
||||
private requestEvent(channelName: string, name: string, arg?: any): Event<any> {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./media/quickInput';
|
||||
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IQuickPickItem, IPickOptions, IInputOptions, IQuickNavigateConfiguration, IQuickPick, IQuickInput, IQuickInputButton, IInputBox, IQuickPickItemButtonEvent, QuickPickInput, IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent, NO_KEY_MODS, ItemActivation } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { QuickInputList, QuickInputListFocus } from './quickInputList';
|
||||
@@ -20,11 +20,9 @@ import { dispose, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { ActionBar, ActionViewItem } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||
import { Action } from 'vs/base/common/actions';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { equals } from 'vs/base/common/arrays';
|
||||
import { TimeoutTimer } from 'vs/base/common/async';
|
||||
import { getIconClass } from 'vs/base/parts/quickinput/browser/quickInputUtils';
|
||||
import { registerAndGetAmdImageURL } from 'vs/base/common/amd';
|
||||
import { IListVirtualDelegate, IListRenderer } from 'vs/base/browser/ui/list/list';
|
||||
import { List, IListOptions, IListStyles } from 'vs/base/browser/ui/list/listWidget';
|
||||
import { IInputBoxStyles } from 'vs/base/browser/ui/inputbox/inputBox';
|
||||
@@ -70,10 +68,7 @@ const $ = dom.$;
|
||||
type Writeable<T> = { -readonly [P in keyof T]: T[P] };
|
||||
|
||||
const backButton = {
|
||||
iconPath: {
|
||||
dark: URI.parse(registerAndGetAmdImageURL('vs/base/parts/quickinput/browser/media/arrow-left-dark.svg')),
|
||||
light: URI.parse(registerAndGetAmdImageURL('vs/base/parts/quickinput/browser/media/arrow-left-light.svg'))
|
||||
},
|
||||
iconClass: 'codicon-arrow-left',
|
||||
tooltip: localize('quickInput.back', "Back"),
|
||||
handle: -1 // TODO
|
||||
};
|
||||
@@ -391,7 +386,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
private _matchOnLabel = true;
|
||||
private _sortByLabel = true;
|
||||
private _autoFocusOnList = true;
|
||||
private _autoFocusSecondEntry = false;
|
||||
private _itemActivation = ItemActivation.FIRST;
|
||||
private _activeItems: T[] = [];
|
||||
private activeItemsUpdated = false;
|
||||
private activeItemsToConfirm: T[] | null = [];
|
||||
@@ -527,12 +522,12 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.update();
|
||||
}
|
||||
|
||||
get autoFocusSecondEntry() {
|
||||
return this._autoFocusSecondEntry;
|
||||
get itemActivation() {
|
||||
return this._itemActivation;
|
||||
}
|
||||
|
||||
set autoFocusSecondEntry(autoFocusSecondEntry: boolean) {
|
||||
this._autoFocusSecondEntry = autoFocusSecondEntry;
|
||||
set itemActivation(itemActivation: ItemActivation) {
|
||||
this._itemActivation = itemActivation;
|
||||
}
|
||||
|
||||
get activeItems() {
|
||||
@@ -879,11 +874,18 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
|
||||
this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
|
||||
this.ui.visibleCount.setCount(this.ui.list.getVisibleCount());
|
||||
this.ui.count.setCount(this.ui.list.getCheckedCount());
|
||||
if (this._autoFocusSecondEntry) {
|
||||
this.ui.list.focus(QuickInputListFocus.Second);
|
||||
this._autoFocusSecondEntry = false; // only valid once, then unset
|
||||
} else {
|
||||
this.trySelectFirst();
|
||||
switch (this._itemActivation) {
|
||||
case ItemActivation.SECOND:
|
||||
this.ui.list.focus(QuickInputListFocus.Second);
|
||||
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
|
||||
break;
|
||||
case ItemActivation.LAST:
|
||||
this.ui.list.focus(QuickInputListFocus.Last);
|
||||
this._itemActivation = ItemActivation.FIRST; // only valid once, then unset
|
||||
break;
|
||||
default:
|
||||
this.trySelectFirst();
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (this.ui.container.classList.contains('show-checkboxes') !== !!this.canSelectMany) {
|
||||
|
||||
@@ -496,7 +496,7 @@ export class QuickInputList {
|
||||
}
|
||||
|
||||
set enabled(value: boolean) {
|
||||
this.list.getHTMLElement().style.pointerEvents = value ? null : 'none';
|
||||
this.list.getHTMLElement().style.pointerEvents = value ? '' : 'none';
|
||||
}
|
||||
|
||||
focus(what: QuickInputListFocus): void {
|
||||
|
||||
@@ -182,6 +182,12 @@ export interface IQuickPickAcceptEvent {
|
||||
inBackground: boolean;
|
||||
}
|
||||
|
||||
export enum ItemActivation {
|
||||
FIRST = 1,
|
||||
SECOND,
|
||||
LAST
|
||||
}
|
||||
|
||||
export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
|
||||
|
||||
value: string;
|
||||
@@ -237,20 +243,17 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
|
||||
|
||||
autoFocusOnList: boolean;
|
||||
|
||||
/**
|
||||
* If enabled, will try to select the second entry of the picks
|
||||
* once they appear instead of the first one. This is useful
|
||||
* e.g. when `quickNavigate` is enabled to be able to select
|
||||
* a previous entry by just releasing the quick nav keys.
|
||||
*/
|
||||
autoFocusSecondEntry: boolean;
|
||||
|
||||
quickNavigate: IQuickNavigateConfiguration | undefined;
|
||||
|
||||
activeItems: ReadonlyArray<T>;
|
||||
|
||||
readonly onDidChangeActive: Event<T[]>;
|
||||
|
||||
/**
|
||||
* Allows to control which entry should be activated by default.
|
||||
*/
|
||||
itemActivation: ItemActivation;
|
||||
|
||||
selectedItems: ReadonlyArray<T>;
|
||||
|
||||
readonly onDidChangeSelection: Event<T[]>;
|
||||
|
||||
@@ -106,6 +106,40 @@ suite('History Navigator', () => {
|
||||
assert.deepEqual(['2', '3', '1'], toArray(testObject));
|
||||
});
|
||||
|
||||
test('previous returns null if the current position is the first one', () => {
|
||||
const testObject = new HistoryNavigator(['1', '2', '3']);
|
||||
|
||||
testObject.first();
|
||||
|
||||
assert.deepEqual(testObject.previous(), null);
|
||||
});
|
||||
|
||||
test('previous returns object if the current position is not the first one', () => {
|
||||
const testObject = new HistoryNavigator(['1', '2', '3']);
|
||||
|
||||
testObject.first();
|
||||
testObject.next();
|
||||
|
||||
assert.deepEqual(testObject.previous(), '1');
|
||||
});
|
||||
|
||||
test('next returns null if the current position is the last one', () => {
|
||||
const testObject = new HistoryNavigator(['1', '2', '3']);
|
||||
|
||||
testObject.last();
|
||||
|
||||
assert.deepEqual(testObject.next(), null);
|
||||
});
|
||||
|
||||
test('next returns object if the current position is not the last one', () => {
|
||||
const testObject = new HistoryNavigator(['1', '2', '3']);
|
||||
|
||||
testObject.last();
|
||||
testObject.previous();
|
||||
|
||||
assert.deepEqual(testObject.next(), '3');
|
||||
});
|
||||
|
||||
test('clear', () => {
|
||||
const testObject = new HistoryNavigator(['a', 'b', 'c']);
|
||||
assert.equal(testObject.previous(), 'c');
|
||||
|
||||
@@ -503,4 +503,65 @@ suite('URI', () => {
|
||||
// }
|
||||
// console.profileEnd();
|
||||
});
|
||||
function assertJoined(base: string, fragment: string, expected: string, checkWithUrl: boolean = true) {
|
||||
const baseUri = URI.parse(base);
|
||||
const newUri = URI.joinPath(baseUri, fragment);
|
||||
const actual = newUri.toString(true);
|
||||
assert.equal(actual, expected);
|
||||
|
||||
if (checkWithUrl) {
|
||||
const actualUrl = new URL(fragment, base).href;
|
||||
assert.equal(actualUrl, expected, 'DIFFERENT from URL');
|
||||
}
|
||||
}
|
||||
test('URI#joinPath', function () {
|
||||
|
||||
assertJoined(('file:///foo/'), '../../bazz', 'file:///bazz');
|
||||
assertJoined(('file:///foo'), '../../bazz', 'file:///bazz');
|
||||
assertJoined(('file:///foo'), '../../bazz', 'file:///bazz');
|
||||
assertJoined(('file:///foo/bar/'), './bazz', 'file:///foo/bar/bazz');
|
||||
assertJoined(('file:///foo/bar'), './bazz', 'file:///foo/bar/bazz', false);
|
||||
assertJoined(('file:///foo/bar'), 'bazz', 'file:///foo/bar/bazz', false);
|
||||
|
||||
// "auto-path" scheme
|
||||
assertJoined(('file:'), 'bazz', 'file:///bazz');
|
||||
assertJoined(('http://domain'), 'bazz', 'http://domain/bazz');
|
||||
assertJoined(('https://domain'), 'bazz', 'https://domain/bazz');
|
||||
assertJoined(('http:'), 'bazz', 'http:/bazz', false);
|
||||
assertJoined(('https:'), 'bazz', 'https:/bazz', false);
|
||||
|
||||
// no "auto-path" scheme with and w/o paths
|
||||
assertJoined(('foo:/'), 'bazz', 'foo:/bazz');
|
||||
assertJoined(('foo://bar/'), 'bazz', 'foo://bar/bazz');
|
||||
|
||||
// no "auto-path" + no path -> error
|
||||
assert.throws(() => assertJoined(('foo:'), 'bazz', ''));
|
||||
assert.throws(() => new URL('bazz', 'foo:'));
|
||||
assert.throws(() => assertJoined(('foo://bar'), 'bazz', ''));
|
||||
assert.throws(() => new URL('bazz', 'foo://bar'));
|
||||
});
|
||||
|
||||
test('URI#joinPath (posix)', function () {
|
||||
if (isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
assertJoined(('file:///c:/foo/'), '../../bazz', 'file:///bazz', false);
|
||||
assertJoined(('file://server/share/c:/'), '../../bazz', 'file://server/bazz', false);
|
||||
assertJoined(('file://server/share/c:'), '../../bazz', 'file://server/bazz', false);
|
||||
|
||||
assertJoined(('file://ser/foo/'), '../../bazz', 'file://ser/bazz');
|
||||
assertJoined(('file://ser/foo'), '../../bazz', 'file://ser/bazz');
|
||||
});
|
||||
|
||||
test('URI#joinPath (windows)', function () {
|
||||
if (!isWindows) {
|
||||
this.skip();
|
||||
}
|
||||
assertJoined(('file:///c:/foo/'), '../../bazz', 'file:///c:/bazz', false);
|
||||
assertJoined(('file://server/share/c:/'), '../../bazz', 'file://server/share/bazz', false);
|
||||
assertJoined(('file://server/share/c:'), '../../bazz', 'file://server/share/bazz', false);
|
||||
|
||||
assertJoined(('file://ser/foo/'), '../../bazz', 'file://ser/foo/bazz', false);
|
||||
assertJoined(('file://ser/foo'), '../../bazz', 'file://ser/foo/bazz', false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -52,7 +52,7 @@ import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { IUserDataSyncService, IUserDataSyncStoreService, registerConfiguration, IUserDataSyncLogService, IUserDataSyncUtilService, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService } 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, UserDataAutoSyncChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
import { UserDataSyncChannel, UserDataSyncUtilServiceClient, UserDataAutoSyncChannel, StorageKeysSyncRegistryChannelClient } 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';
|
||||
@@ -67,6 +67,7 @@ import { UserDataSyncEnablementService } from 'vs/platform/userDataSync/common/u
|
||||
import { IAuthenticationTokenService, AuthenticationTokenService } from 'vs/platform/authentication/common/authentication';
|
||||
import { AuthenticationTokenServiceChannel } from 'vs/platform/authentication/common/authenticationIpc';
|
||||
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
export interface ISharedProcessConfiguration {
|
||||
readonly machineId: string;
|
||||
@@ -140,6 +141,8 @@ async function main(server: Server, initData: ISharedProcessInitData, configurat
|
||||
services.set(IStorageService, storageService);
|
||||
disposables.add(toDisposable(() => storageService.flush()));
|
||||
|
||||
services.set(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryChannelClient(mainProcessService.getChannel('storageKeysSyncRegistryService')));
|
||||
|
||||
services.set(IEnvironmentService, environmentService);
|
||||
services.set(IProductService, { _serviceBrand: undefined, ...product });
|
||||
services.set(ILogService, logService);
|
||||
|
||||
@@ -77,6 +77,8 @@ import { IDialogMainService, DialogMainService } from 'vs/platform/dialogs/elect
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
import { parseArgs, OPTIONS } from 'vs/platform/environment/node/argv';
|
||||
import { coalesce } from 'vs/base/common/arrays';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { StorageKeysSyncRegistryChannel } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
|
||||
export class CodeApplication extends Disposable {
|
||||
private windowsMainService: IWindowsMainService | undefined;
|
||||
@@ -567,6 +569,11 @@ export class CodeApplication extends Disposable {
|
||||
electronIpcServer.registerChannel('storage', storageChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('storage', storageChannel));
|
||||
|
||||
const storageKeysSyncRegistryService = accessor.get(IStorageKeysSyncRegistryService);
|
||||
const storageKeysSyncChannel = new StorageKeysSyncRegistryChannel(storageKeysSyncRegistryService);
|
||||
electronIpcServer.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('storageKeysSyncRegistryService', storageKeysSyncChannel));
|
||||
|
||||
const loggerChannel = new LoggerChannel(accessor.get(ILogService));
|
||||
electronIpcServer.registerChannel('logger', loggerChannel);
|
||||
sharedProcessClient.then(client => client.registerChannel('logger', loggerChannel));
|
||||
|
||||
@@ -46,6 +46,7 @@ import { FileService } from 'vs/platform/files/common/fileService';
|
||||
import { DiskFileSystemProvider } from 'vs/platform/files/node/diskFileSystemProvider';
|
||||
import { Schemas } from 'vs/base/common/network';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
class ExpectedError extends Error {
|
||||
readonly isExpected = true;
|
||||
@@ -162,6 +163,7 @@ class CodeMain {
|
||||
services.set(IRequestService, new SyncDescriptor(RequestMainService));
|
||||
services.set(IThemeMainService, new SyncDescriptor(ThemeMainService));
|
||||
services.set(ISignService, new SyncDescriptor(SignService));
|
||||
services.set(IStorageKeysSyncRegistryService, new SyncDescriptor(StorageKeysSyncRegistryService));
|
||||
|
||||
return [new InstantiationService(services, true), instanceEnvironment];
|
||||
}
|
||||
|
||||
@@ -42,8 +42,8 @@ class MsPointerHandler extends MouseHandler implements IDisposable {
|
||||
constructor(context: ViewContext, viewController: ViewController, viewHelper: IPointerHandlerHelper) {
|
||||
super(context, viewController, viewHelper);
|
||||
|
||||
this.viewHelper.linesContentDomNode.style.msTouchAction = 'none';
|
||||
this.viewHelper.linesContentDomNode.style.msContentZooming = 'none';
|
||||
(this.viewHelper.linesContentDomNode.style as any).msTouchAction = 'none';
|
||||
(this.viewHelper.linesContentDomNode.style as any).msContentZooming = 'none';
|
||||
|
||||
// TODO@Alex -> this expects that the view is added in 100 ms, might not be the case
|
||||
// This handler should be added when the dom node is in the dom tree
|
||||
|
||||
@@ -349,7 +349,7 @@ export class TextAreaHandler extends ViewPart {
|
||||
private _getAriaLabel(options: IComputedEditorOptions): string {
|
||||
const accessibilitySupport = options.get(EditorOption.accessibilitySupport);
|
||||
if (accessibilitySupport === AccessibilitySupport.Disabled) {
|
||||
return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press Alt+F1 for options.");
|
||||
return nls.localize('accessibilityOffAriaLabel', "The editor is not accessible at this time. Press {0} for options.", platform.isLinux ? 'Shift+Alt+F1' : 'Alt+F1');
|
||||
}
|
||||
return options.get(EditorOption.ariaLabel);
|
||||
}
|
||||
|
||||
@@ -23,6 +23,8 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
protected _contentLeft: number;
|
||||
protected _contentWidth: number;
|
||||
protected _selectionIsEmpty: boolean;
|
||||
protected _renderLineHightlightOnlyWhenFocus: boolean;
|
||||
protected _focused: boolean;
|
||||
private _cursorLineNumbers: number[];
|
||||
private _selections: Selection[];
|
||||
private _renderData: string[] | null;
|
||||
@@ -35,9 +37,11 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
this._lineHeight = options.get(EditorOption.lineHeight);
|
||||
this._renderLineHighlight = options.get(EditorOption.renderLineHighlight);
|
||||
this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus);
|
||||
this._contentLeft = layoutInfo.contentLeft;
|
||||
this._contentWidth = layoutInfo.contentWidth;
|
||||
this._selectionIsEmpty = true;
|
||||
this._focused = false;
|
||||
this._cursorLineNumbers = [];
|
||||
this._selections = [];
|
||||
this._renderData = null;
|
||||
@@ -81,6 +85,7 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
const layoutInfo = options.get(EditorOption.layoutInfo);
|
||||
this._lineHeight = options.get(EditorOption.lineHeight);
|
||||
this._renderLineHighlight = options.get(EditorOption.renderLineHighlight);
|
||||
this._renderLineHightlightOnlyWhenFocus = options.get(EditorOption.renderLineHighlightOnlyWhenFocus);
|
||||
this._contentLeft = layoutInfo.contentLeft;
|
||||
this._contentWidth = layoutInfo.contentWidth;
|
||||
return true;
|
||||
@@ -104,6 +109,14 @@ export abstract class AbstractLineHighlightOverlay extends DynamicViewOverlay {
|
||||
public onZonesChanged(e: viewEvents.ViewZonesChangedEvent): boolean {
|
||||
return true;
|
||||
}
|
||||
public onFocusChanged(e: viewEvents.ViewFocusChangedEvent): boolean {
|
||||
if (!this._renderLineHightlightOnlyWhenFocus) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this._focused = e.isFocused;
|
||||
return true;
|
||||
}
|
||||
// --- end event handlers
|
||||
|
||||
public prepareRender(ctx: RenderingContext): void {
|
||||
@@ -157,11 +170,13 @@ export class CurrentLineHighlightOverlay extends AbstractLineHighlightOverlay {
|
||||
return (
|
||||
(this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all')
|
||||
&& this._selectionIsEmpty
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
protected _shouldRenderOther(): boolean {
|
||||
return (
|
||||
(this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all')
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -174,12 +189,14 @@ export class CurrentLineMarginHighlightOverlay extends AbstractLineHighlightOver
|
||||
protected _shouldRenderThis(): boolean {
|
||||
return (
|
||||
(this._renderLineHighlight === 'gutter' || this._renderLineHighlight === 'all')
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
protected _shouldRenderOther(): boolean {
|
||||
return (
|
||||
(this._renderLineHighlight === 'line' || this._renderLineHighlight === 'all')
|
||||
&& this._selectionIsEmpty
|
||||
&& (!this._renderLineHightlightOnlyWhenFocus || this._focused)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,6 +546,11 @@ export interface IEditorOptions {
|
||||
* Defaults to all.
|
||||
*/
|
||||
renderLineHighlight?: 'none' | 'gutter' | 'line' | 'all';
|
||||
/**
|
||||
* Control if the current line highlight should be rendered only the editor is focused.
|
||||
* Defaults to false.
|
||||
*/
|
||||
renderLineHighlightOnlyWhenFocus?: boolean;
|
||||
/**
|
||||
* Inserting and deleting whitespace follows tab stops.
|
||||
*/
|
||||
@@ -3414,6 +3419,7 @@ export const enum EditorOption {
|
||||
renderIndentGuides,
|
||||
renderFinalNewline,
|
||||
renderLineHighlight,
|
||||
renderLineHighlightOnlyWhenFocus,
|
||||
renderValidationDecorations,
|
||||
renderWhitespace,
|
||||
revealHorizontalRightPadding,
|
||||
@@ -3856,6 +3862,10 @@ export const EditorOptions = {
|
||||
description: nls.localize('renderLineHighlight', "Controls how the editor should render the current line highlight.")
|
||||
}
|
||||
)),
|
||||
renderLineHighlightOnlyWhenFocus: register(new EditorBooleanOption(
|
||||
EditorOption.renderLineHighlightOnlyWhenFocus, 'renderLineHighlightOnlyWhenFocus', false,
|
||||
{ description: nls.localize('renderLineHighlightOnlyWhenFocus', "Controls if the editor should render the current line highlight only when the editor is focused") }
|
||||
)),
|
||||
renderValidationDecorations: register(new EditorStringEnumOption(
|
||||
EditorOption.renderValidationDecorations, 'renderValidationDecorations',
|
||||
'editable' as 'editable' | 'on' | 'off',
|
||||
|
||||
@@ -245,43 +245,44 @@ export enum EditorOption {
|
||||
renderIndentGuides = 75,
|
||||
renderFinalNewline = 76,
|
||||
renderLineHighlight = 77,
|
||||
renderValidationDecorations = 78,
|
||||
renderWhitespace = 79,
|
||||
revealHorizontalRightPadding = 80,
|
||||
roundedSelection = 81,
|
||||
rulers = 82,
|
||||
scrollbar = 83,
|
||||
scrollBeyondLastColumn = 84,
|
||||
scrollBeyondLastLine = 85,
|
||||
scrollPredominantAxis = 86,
|
||||
selectionClipboard = 87,
|
||||
selectionHighlight = 88,
|
||||
selectOnLineNumbers = 89,
|
||||
showFoldingControls = 90,
|
||||
showUnused = 91,
|
||||
snippetSuggestions = 92,
|
||||
smoothScrolling = 93,
|
||||
stopRenderingLineAfter = 94,
|
||||
suggest = 95,
|
||||
suggestFontSize = 96,
|
||||
suggestLineHeight = 97,
|
||||
suggestOnTriggerCharacters = 98,
|
||||
suggestSelection = 99,
|
||||
tabCompletion = 100,
|
||||
useTabStops = 101,
|
||||
wordSeparators = 102,
|
||||
wordWrap = 103,
|
||||
wordWrapBreakAfterCharacters = 104,
|
||||
wordWrapBreakBeforeCharacters = 105,
|
||||
wordWrapColumn = 106,
|
||||
wordWrapMinified = 107,
|
||||
wrappingIndent = 108,
|
||||
wrappingStrategy = 109,
|
||||
editorClassName = 110,
|
||||
pixelRatio = 111,
|
||||
tabFocusMode = 112,
|
||||
layoutInfo = 113,
|
||||
wrappingInfo = 114
|
||||
renderLineHighlightOnlyWhenFocus = 78,
|
||||
renderValidationDecorations = 79,
|
||||
renderWhitespace = 80,
|
||||
revealHorizontalRightPadding = 81,
|
||||
roundedSelection = 82,
|
||||
rulers = 83,
|
||||
scrollbar = 84,
|
||||
scrollBeyondLastColumn = 85,
|
||||
scrollBeyondLastLine = 86,
|
||||
scrollPredominantAxis = 87,
|
||||
selectionClipboard = 88,
|
||||
selectionHighlight = 89,
|
||||
selectOnLineNumbers = 90,
|
||||
showFoldingControls = 91,
|
||||
showUnused = 92,
|
||||
snippetSuggestions = 93,
|
||||
smoothScrolling = 94,
|
||||
stopRenderingLineAfter = 95,
|
||||
suggest = 96,
|
||||
suggestFontSize = 97,
|
||||
suggestLineHeight = 98,
|
||||
suggestOnTriggerCharacters = 99,
|
||||
suggestSelection = 100,
|
||||
tabCompletion = 101,
|
||||
useTabStops = 102,
|
||||
wordSeparators = 103,
|
||||
wordWrap = 104,
|
||||
wordWrapBreakAfterCharacters = 105,
|
||||
wordWrapBreakBeforeCharacters = 106,
|
||||
wordWrapColumn = 107,
|
||||
wordWrapMinified = 108,
|
||||
wrappingIndent = 109,
|
||||
wrappingStrategy = 110,
|
||||
editorClassName = 111,
|
||||
pixelRatio = 112,
|
||||
tabFocusMode = 113,
|
||||
layoutInfo = 114,
|
||||
wrappingInfo = 115
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -11,7 +11,7 @@ import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { overviewRulerRangeHighlight } from 'vs/editor/common/view/editorColorRegistry';
|
||||
import { IQuickPick, IQuickPickItem, IKeyMods } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IDisposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, DisposableStore, toDisposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { isDiffEditor, getCodeEditor } from 'vs/editor/browser/editorBrowser';
|
||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||
@@ -47,13 +47,12 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
||||
picker.matchOnLabel = picker.matchOnDescription = picker.matchOnDetail = picker.sortByLabel = false;
|
||||
|
||||
// Provide based on current active editor
|
||||
let pickerDisposable = this.doProvide(picker, token);
|
||||
disposables.add(toDisposable(() => pickerDisposable.dispose()));
|
||||
const pickerDisposable = disposables.add(new MutableDisposable());
|
||||
pickerDisposable.value = this.doProvide(picker, token);
|
||||
|
||||
// Re-create whenever the active editor changes
|
||||
disposables.add(this.onDidActiveTextEditorControlChange(() => {
|
||||
pickerDisposable.dispose();
|
||||
pickerDisposable = this.doProvide(picker, token);
|
||||
pickerDisposable.value = this.doProvide(picker, token);
|
||||
}));
|
||||
|
||||
return disposables;
|
||||
@@ -81,7 +80,7 @@ export abstract class AbstractEditorNavigationQuickAccessProvider implements IQu
|
||||
}));
|
||||
|
||||
disposables.add(once(token.onCancellationRequested)(() => {
|
||||
if (lastKnownEditorViewState) {
|
||||
if (lastKnownEditorViewState && editor === this.activeTextEditorControl) {
|
||||
editor.restoreViewState(lastKnownEditorViewState);
|
||||
}
|
||||
}));
|
||||
|
||||
Vendored
+46
-39
@@ -179,11 +179,11 @@ declare namespace monaco {
|
||||
/**
|
||||
* Join a Uri path with path fragments and normalizes the resulting path.
|
||||
*
|
||||
* @param resource The input Uri.
|
||||
* @param uri The input Uri.
|
||||
* @param pathFragment The path fragment to add to the Uri path.
|
||||
* @returns The resulting Uri.
|
||||
*/
|
||||
static joinPaths(resource: Uri, ...pathFragment: string[]): Uri;
|
||||
static joinPath(uri: Uri, ...pathFragment: string[]): Uri;
|
||||
/**
|
||||
* Creates a string representation for this Uri. It's guaranteed that calling
|
||||
* `Uri.parse` with the result of this function creates an Uri which is equal
|
||||
@@ -3081,6 +3081,11 @@ declare namespace monaco.editor {
|
||||
* Defaults to all.
|
||||
*/
|
||||
renderLineHighlight?: 'none' | 'gutter' | 'line' | 'all';
|
||||
/**
|
||||
* Control if the current line highlight should be rendered only the editor is focused.
|
||||
* Defaults to false.
|
||||
*/
|
||||
renderLineHighlightOnlyWhenFocus?: boolean;
|
||||
/**
|
||||
* Inserting and deleting whitespace follows tab stops.
|
||||
*/
|
||||
@@ -3901,43 +3906,44 @@ declare namespace monaco.editor {
|
||||
renderIndentGuides = 75,
|
||||
renderFinalNewline = 76,
|
||||
renderLineHighlight = 77,
|
||||
renderValidationDecorations = 78,
|
||||
renderWhitespace = 79,
|
||||
revealHorizontalRightPadding = 80,
|
||||
roundedSelection = 81,
|
||||
rulers = 82,
|
||||
scrollbar = 83,
|
||||
scrollBeyondLastColumn = 84,
|
||||
scrollBeyondLastLine = 85,
|
||||
scrollPredominantAxis = 86,
|
||||
selectionClipboard = 87,
|
||||
selectionHighlight = 88,
|
||||
selectOnLineNumbers = 89,
|
||||
showFoldingControls = 90,
|
||||
showUnused = 91,
|
||||
snippetSuggestions = 92,
|
||||
smoothScrolling = 93,
|
||||
stopRenderingLineAfter = 94,
|
||||
suggest = 95,
|
||||
suggestFontSize = 96,
|
||||
suggestLineHeight = 97,
|
||||
suggestOnTriggerCharacters = 98,
|
||||
suggestSelection = 99,
|
||||
tabCompletion = 100,
|
||||
useTabStops = 101,
|
||||
wordSeparators = 102,
|
||||
wordWrap = 103,
|
||||
wordWrapBreakAfterCharacters = 104,
|
||||
wordWrapBreakBeforeCharacters = 105,
|
||||
wordWrapColumn = 106,
|
||||
wordWrapMinified = 107,
|
||||
wrappingIndent = 108,
|
||||
wrappingStrategy = 109,
|
||||
editorClassName = 110,
|
||||
pixelRatio = 111,
|
||||
tabFocusMode = 112,
|
||||
layoutInfo = 113,
|
||||
wrappingInfo = 114
|
||||
renderLineHighlightOnlyWhenFocus = 78,
|
||||
renderValidationDecorations = 79,
|
||||
renderWhitespace = 80,
|
||||
revealHorizontalRightPadding = 81,
|
||||
roundedSelection = 82,
|
||||
rulers = 83,
|
||||
scrollbar = 84,
|
||||
scrollBeyondLastColumn = 85,
|
||||
scrollBeyondLastLine = 86,
|
||||
scrollPredominantAxis = 87,
|
||||
selectionClipboard = 88,
|
||||
selectionHighlight = 89,
|
||||
selectOnLineNumbers = 90,
|
||||
showFoldingControls = 91,
|
||||
showUnused = 92,
|
||||
snippetSuggestions = 93,
|
||||
smoothScrolling = 94,
|
||||
stopRenderingLineAfter = 95,
|
||||
suggest = 96,
|
||||
suggestFontSize = 97,
|
||||
suggestLineHeight = 98,
|
||||
suggestOnTriggerCharacters = 99,
|
||||
suggestSelection = 100,
|
||||
tabCompletion = 101,
|
||||
useTabStops = 102,
|
||||
wordSeparators = 103,
|
||||
wordWrap = 104,
|
||||
wordWrapBreakAfterCharacters = 105,
|
||||
wordWrapBreakBeforeCharacters = 106,
|
||||
wordWrapColumn = 107,
|
||||
wordWrapMinified = 108,
|
||||
wrappingIndent = 109,
|
||||
wrappingStrategy = 110,
|
||||
editorClassName = 111,
|
||||
pixelRatio = 112,
|
||||
tabFocusMode = 113,
|
||||
layoutInfo = 114,
|
||||
wrappingInfo = 115
|
||||
}
|
||||
export const EditorOptions: {
|
||||
acceptSuggestionOnCommitCharacter: IEditorOption<EditorOption.acceptSuggestionOnCommitCharacter, boolean>;
|
||||
@@ -4018,6 +4024,7 @@ declare namespace monaco.editor {
|
||||
renderIndentGuides: IEditorOption<EditorOption.renderIndentGuides, boolean>;
|
||||
renderFinalNewline: IEditorOption<EditorOption.renderFinalNewline, boolean>;
|
||||
renderLineHighlight: IEditorOption<EditorOption.renderLineHighlight, 'all' | 'line' | 'none' | 'gutter'>;
|
||||
renderLineHighlightOnlyWhenFocus: IEditorOption<EditorOption.renderLineHighlightOnlyWhenFocus, boolean>;
|
||||
renderValidationDecorations: IEditorOption<EditorOption.renderValidationDecorations, 'on' | 'off' | 'editable'>;
|
||||
renderWhitespace: IEditorOption<EditorOption.renderWhitespace, 'all' | 'none' | 'boundary' | 'selection'>;
|
||||
revealHorizontalRightPadding: IEditorOption<EditorOption.revealHorizontalRightPadding, number>;
|
||||
|
||||
@@ -43,16 +43,14 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'goto': { type: 'boolean', cat: 'o', alias: 'g', args: 'file:line[:character]', description: localize('goto', "Open a file at the path on the specified line and character position.") },
|
||||
'new-window': { type: 'boolean', cat: 'o', alias: 'n', description: localize('newWindow', "Force to open a new window.") },
|
||||
'reuse-window': { type: 'boolean', cat: 'o', alias: 'r', description: localize('reuseWindow', "Force to open a file or folder in an already opened window.") },
|
||||
'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") },
|
||||
'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") },
|
||||
'wait': { type: 'boolean', cat: 'o', alias: 'w', description: localize('wait', "Wait for the files to be closed before returning.") },
|
||||
'waitMarkerFilePath': { type: 'string' },
|
||||
'locale': { type: 'string', cat: 'o', args: 'locale', description: localize('locale', "The locale to use (e.g. en-US or zh-TW).") },
|
||||
'user-data-dir': { type: 'string', cat: 'o', args: 'dir', description: localize('userDataDir', "Specifies the directory that user data is kept in. Can be used to open multiple distinct instances of Code.") },
|
||||
'version': { type: 'boolean', cat: 'o', alias: 'v', description: localize('version', "Print version.") },
|
||||
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
|
||||
'telemetry': { type: 'boolean', cat: 'o', description: localize('telemetry', "Shows all telemetry events which VS code collects.") },
|
||||
'folder-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('folderUri', "Opens a window with given folder uri(s)") },
|
||||
'file-uri': { type: 'string[]', cat: 'o', args: 'uri', description: localize('fileUri', "Opens a window with given file uri(s)") },
|
||||
'sync': { type: 'string', cat: 'o', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] },
|
||||
'help': { type: 'boolean', cat: 'o', alias: 'h', description: localize('help', "Print usage.") },
|
||||
|
||||
'extensions-dir': { type: 'string', deprecates: 'extensionHomePath', cat: 'e', args: 'dir', description: localize('extensionHomePath', "Set the root path for extensions.") },
|
||||
'builtin-extensions-dir': { type: 'string' },
|
||||
@@ -63,6 +61,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'uninstall-extension': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('uninstallExtension', "Uninstalls an extension.") },
|
||||
'enable-proposed-api': { type: 'string[]', cat: 'e', args: 'extension-id', description: localize('experimentalApis', "Enables proposed API features for extensions. Can receive one or more extension IDs to enable individually.") },
|
||||
|
||||
'version': { type: 'boolean', cat: 't', alias: 'v', description: localize('version', "Print version.") },
|
||||
'verbose': { type: 'boolean', cat: 't', description: localize('verbose', "Print verbose output (implies --wait).") },
|
||||
'log': { type: 'string', cat: 't', args: 'level', description: localize('log', "Log level to use. Default is 'info'. Allowed values are 'critical', 'error', 'warn', 'info', 'debug', 'trace', 'off'.") },
|
||||
'status': { type: 'boolean', alias: 's', cat: 't', description: localize('status', "Print process usage and diagnostics information.") },
|
||||
@@ -71,6 +70,7 @@ export const OPTIONS: OptionDescriptions<Required<ParsedArgs>> = {
|
||||
'prof-startup-prefix': { type: 'string' },
|
||||
'disable-extensions': { type: 'boolean', deprecates: 'disableExtensions', cat: 't', description: localize('disableExtensions', "Disable all installed extensions.") },
|
||||
'disable-extension': { type: 'string[]', cat: 't', args: 'extension-id', description: localize('disableExtension', "Disable an extension.") },
|
||||
'sync': { type: 'string', cat: 't', description: localize('turn sync', "Turn sync on or off"), args: ['on', 'off'] },
|
||||
|
||||
'inspect-extensions': { type: 'string', deprecates: 'debugPluginHost', args: 'port', cat: 't', description: localize('inspect-extensions', "Allow debugging and profiling of extensions. Check the developer tools for the connection URI.") },
|
||||
'inspect-brk-extensions': { type: 'string', deprecates: 'debugBrkPluginHost', args: 'port', cat: 't', description: localize('inspect-brk-extensions', "Allow debugging and profiling of extensions with the extension host being paused after start. Check the developer tools for the connection URI.") },
|
||||
|
||||
@@ -86,7 +86,7 @@ function readManifest(extensionPath: string): Promise<{ manifest: IExtensionMani
|
||||
.then(raw => JSON.parse(raw))
|
||||
];
|
||||
|
||||
return Promise.all<any>(promises).then(([{ manifest, metadata }, translations]) => {
|
||||
return Promise.all(promises).then(([{ manifest, metadata }, translations]) => {
|
||||
return {
|
||||
manifest: localizeManifest(manifest, translations),
|
||||
metadata
|
||||
@@ -348,7 +348,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
|
||||
this.downloadInstallableExtension(extension, operation)
|
||||
.then(installableExtension => this.installExtension(installableExtension, ExtensionType.User, cancellationToken)
|
||||
.then(local => pfs.rimraf(installableExtension.zipPath).finally(() => null).then(() => local)))
|
||||
.then(local => pfs.rimraf(installableExtension.zipPath).finally(() => { }).then(() => local)))
|
||||
.then(local => this.installDependenciesAndPackExtensions(local, existingExtension)
|
||||
.then(() => local, error => this.uninstall(local, true).then(() => Promise.reject(error), () => Promise.reject(error))))
|
||||
.then(
|
||||
@@ -503,7 +503,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
() => this.logService.info('Renamed to', renamePath),
|
||||
e => {
|
||||
this.logService.info('Rename failed. Deleting from extracted location', extractPath);
|
||||
return pfs.rimraf(extractPath).finally(() => null).then(() => Promise.reject(e));
|
||||
return pfs.rimraf(extractPath).finally(() => { }).then(() => Promise.reject(e));
|
||||
}));
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ export class ExtensionManagementService extends Disposable implements IExtension
|
||||
() => extract(zipPath, extractPath, { sourcePath: 'extension', overwrite: true }, token)
|
||||
.then(
|
||||
() => this.logService.info(`Extracted extension to ${extractPath}:`, identifier.id),
|
||||
e => pfs.rimraf(extractPath).finally(() => null)
|
||||
e => pfs.rimraf(extractPath).finally(() => { })
|
||||
.then(() => Promise.reject(new ExtensionManagementError(e.message, e instanceof ExtractError && e.type ? e.type : INSTALL_ERROR_EXTRACTING)))),
|
||||
e => Promise.reject(new ExtensionManagementError(this.joinErrors(e).message, INSTALL_ERROR_DELETING)));
|
||||
}
|
||||
|
||||
@@ -55,12 +55,17 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
// Ask subclass for all command picks
|
||||
const allCommandPicks = await this.getCommandPicks(disposables, token);
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Filter
|
||||
const filteredCommandPicks: ICommandQuickPick[] = [];
|
||||
for (const commandPick of allCommandPicks) {
|
||||
const labelHighlights = withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.label));
|
||||
const aliasHighlights = commandPick.commandAlias ? withNullAsUndefined(AbstractCommandsQuickAccessProvider.WORD_FILTER(filter, commandPick.commandAlias)) : undefined;
|
||||
|
||||
// Add if matching in label or alias
|
||||
if (labelHighlights || aliasHighlights) {
|
||||
commandPick.highlights = {
|
||||
label: labelHighlights,
|
||||
@@ -69,6 +74,11 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
|
||||
filteredCommandPicks.push(commandPick);
|
||||
}
|
||||
|
||||
// Also add if we have a 100% command ID match
|
||||
else if (filter === commandPick.commandId) {
|
||||
filteredCommandPicks.push(commandPick);
|
||||
}
|
||||
}
|
||||
|
||||
// Add description to commands that have duplicate labels
|
||||
@@ -130,7 +140,7 @@ export abstract class AbstractCommandsQuickAccessProvider extends PickerQuickAcc
|
||||
commandPicks.push({
|
||||
...commandPick,
|
||||
ariaLabel,
|
||||
detail: this.options.showAlias ? commandPick.commandAlias : undefined,
|
||||
detail: this.options.showAlias && commandPick.commandAlias !== commandPick.label ? commandPick.commandAlias : undefined,
|
||||
keybinding,
|
||||
accept: async () => {
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickI
|
||||
import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation';
|
||||
import { IQuickPickSeparator, IKeyMods, IQuickPickAcceptEvent } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
import { IQuickAccessProvider } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { IDisposable, DisposableStore, Disposable } from 'vs/base/common/lifecycle';
|
||||
import { IDisposable, DisposableStore, Disposable, MutableDisposable } from 'vs/base/common/lifecycle';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
|
||||
export enum TriggerAction {
|
||||
@@ -90,7 +90,9 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
||||
|
||||
// Set initial picks and update on type
|
||||
let picksCts: CancellationTokenSource | undefined = undefined;
|
||||
const picksDisposable = disposables.add(new MutableDisposable());
|
||||
const updatePickerItems = async () => {
|
||||
const picksDisposables = picksDisposable.value = new DisposableStore();
|
||||
|
||||
// Cancel any previous ask for picks and busy
|
||||
picksCts?.dispose(true);
|
||||
@@ -101,7 +103,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
||||
|
||||
// Collect picks and support both long running and short or combined
|
||||
const picksToken = picksCts.token;
|
||||
const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), disposables.add(new DisposableStore()), picksToken);
|
||||
const res = this.getPicks(picker.value.substr(this.prefix.length).trim(), picksDisposables, picksToken);
|
||||
|
||||
// No Picks
|
||||
if (res === null) {
|
||||
@@ -191,6 +193,7 @@ export abstract class PickerQuickAccessProvider<T extends IPickerQuickAccessItem
|
||||
if (!event.inBackground) {
|
||||
picker.hide(); // hide picker unless we accept in background
|
||||
}
|
||||
|
||||
item.accept(picker.keyMods, event);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IQuickInputService, IQuickPick, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickInputService, IQuickPick, IQuickPickItem, ItemActivation } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { Disposable, DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { IQuickAccessController, IQuickAccessProvider, IQuickAccessRegistry, Extensions, IQuickAccessProviderDescriptor, IQuickAccessOptions, DefaultQuickAccessFilterValue } from 'vs/platform/quickinput/common/quickAccess';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
@@ -44,16 +44,13 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
// Find provider for the value to show
|
||||
const [provider, descriptor] = this.getOrInstantiateProvider(value);
|
||||
|
||||
// Return early if quick access is already showing on that
|
||||
// same prefix and simply take over the filter value if it
|
||||
// is more specific and select it for the user to be able
|
||||
// to type over
|
||||
// Return early if quick access is already showing on that same prefix
|
||||
const visibleQuickAccess = this.visibleQuickAccess;
|
||||
const visibleDescriptor = visibleQuickAccess?.descriptor;
|
||||
if (visibleQuickAccess && descriptor && visibleDescriptor === descriptor) {
|
||||
|
||||
// Take over the value only if it is not matching
|
||||
// the existing provider prefix or we are to preserve
|
||||
// Apply value only if it is more specific than the prefix
|
||||
// from the provider and we are not instructed to preserve
|
||||
if (value !== descriptor.prefix && !options?.preserveFilterValue) {
|
||||
visibleQuickAccess.picker.value = value;
|
||||
}
|
||||
@@ -77,8 +74,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
}
|
||||
}
|
||||
|
||||
// If the new provider wants to preserve the filter, take it's last remembered value
|
||||
// If the new provider wants to define the filter, take it as is
|
||||
// Otherwise, take a default value as instructed
|
||||
if (!newValue) {
|
||||
const defaultFilterValue = provider?.defaultFilterValue;
|
||||
if (defaultFilterValue === DefaultQuickAccessFilterValue.LAST) {
|
||||
@@ -102,12 +98,12 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
picker.placeholder = descriptor?.placeholder;
|
||||
picker.quickNavigate = options?.quickNavigateConfiguration;
|
||||
picker.hideInput = !!picker.quickNavigate && !visibleQuickAccess; // only hide input if there was no picker opened already
|
||||
picker.autoFocusSecondEntry = !!options?.quickNavigateConfiguration || !!options?.autoFocus?.autoFocusSecondEntry;
|
||||
picker.itemActivation = options?.itemActivation || (options?.quickNavigateConfiguration ? ItemActivation.SECOND : ItemActivation.FIRST);
|
||||
picker.contextKey = descriptor?.contextKey;
|
||||
picker.filterValue = (value: string) => value.substring(descriptor ? descriptor.prefix.length : 0);
|
||||
|
||||
// Register listeners
|
||||
const cancellationToken = this.registerPickerListeners(disposables, picker, provider, descriptor, value);
|
||||
const cancellationToken = this.registerPickerListeners(picker, provider, descriptor, value, disposables);
|
||||
|
||||
// Ask provider to fill the picker as needed if we have one
|
||||
if (provider) {
|
||||
@@ -136,7 +132,7 @@ export class QuickAccessController extends Disposable implements IQuickAccessCon
|
||||
picker.valueSelection = valueSelection;
|
||||
}
|
||||
|
||||
private registerPickerListeners(disposables: DisposableStore, picker: IQuickPick<IQuickPickItem>, provider: IQuickAccessProvider | undefined, descriptor: IQuickAccessProviderDescriptor | undefined, value: string): CancellationToken {
|
||||
private registerPickerListeners(picker: IQuickPick<IQuickPickItem>, provider: IQuickAccessProvider | undefined, descriptor: IQuickAccessProviderDescriptor | undefined, value: string, disposables: DisposableStore): CancellationToken {
|
||||
|
||||
// Remember as last visible picker and clean up once picker get's disposed
|
||||
const visibleQuickAccess = this.visibleQuickAccess = { picker, descriptor, value };
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { first, coalesce } from 'vs/base/common/arrays';
|
||||
import { startsWith } from 'vs/base/common/strings';
|
||||
import { IDisposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ItemActivation } from 'vs/base/parts/quickinput/common/quickInput';
|
||||
|
||||
export interface IQuickAccessOptions {
|
||||
|
||||
@@ -18,9 +19,10 @@ export interface IQuickAccessOptions {
|
||||
quickNavigateConfiguration?: IQuickNavigateConfiguration;
|
||||
|
||||
/**
|
||||
* Wether to select the second pick item by default instead of the first.
|
||||
* Allows to configure a different item activation strategy.
|
||||
* By default the first item in the list will get activated.
|
||||
*/
|
||||
autoFocus?: { autoFocusSecondEntry?: boolean }
|
||||
itemActivation?: ItemActivation
|
||||
}
|
||||
|
||||
export interface IQuickAccessController {
|
||||
|
||||
@@ -4,63 +4,120 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as objects from 'vs/base/common/objects';
|
||||
import { IGlobalState } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStorageValue } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { IStringDictionary } from 'vs/base/common/collections';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
|
||||
export function merge(localGloablState: IGlobalState, remoteGlobalState: IGlobalState | null, lastSyncGlobalState: IGlobalState | null): { local?: IGlobalState, remote?: IGlobalState } {
|
||||
if (!remoteGlobalState) {
|
||||
return { remote: localGloablState };
|
||||
}
|
||||
|
||||
const { local: localArgv, remote: remoteArgv } = doMerge(localGloablState.argv, remoteGlobalState.argv, lastSyncGlobalState ? lastSyncGlobalState.argv : null);
|
||||
const { local: localStorage, remote: remoteStorage } = doMerge(localGloablState.storage, remoteGlobalState.storage, lastSyncGlobalState ? lastSyncGlobalState.storage : null);
|
||||
const local: IGlobalState | undefined = localArgv || localStorage ? { argv: localArgv || localGloablState.argv, storage: localStorage || localGloablState.storage } : undefined;
|
||||
const remote: IGlobalState | undefined = remoteArgv || remoteStorage ? { argv: remoteArgv || remoteGlobalState.argv, storage: remoteStorage || remoteGlobalState.storage } : undefined;
|
||||
|
||||
return { local, remote };
|
||||
export interface IMergeResult {
|
||||
local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
|
||||
remote: IStringDictionary<IStorageValue> | null;
|
||||
}
|
||||
|
||||
function doMerge(local: IStringDictionary<any>, remote: IStringDictionary<any>, base: IStringDictionary<any> | null): { local?: IStringDictionary<any>, remote?: IStringDictionary<any> } {
|
||||
const localToRemote = compare(local, remote);
|
||||
export function merge(localStorage: IStringDictionary<IStorageValue>, remoteStorage: IStringDictionary<IStorageValue> | null, baseStorage: IStringDictionary<IStorageValue> | null, storageKeys: ReadonlyArray<IStorageKey>, logService: ILogService): IMergeResult {
|
||||
if (!remoteStorage) {
|
||||
return { remote: localStorage, local: { added: {}, removed: [], updated: {} } };
|
||||
}
|
||||
|
||||
const localToRemote = compare(localStorage, remoteStorage);
|
||||
if (localToRemote.added.size === 0 && localToRemote.removed.size === 0 && localToRemote.updated.size === 0) {
|
||||
// No changes found between local and remote.
|
||||
return {};
|
||||
return { remote: null, local: { added: {}, removed: [], updated: {} } };
|
||||
}
|
||||
|
||||
const baseToRemote = base ? compare(base, remote) : { added: Object.keys(remote).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
if (baseToRemote.added.size === 0 && baseToRemote.removed.size === 0 && baseToRemote.updated.size === 0) {
|
||||
// No changes found between base and remote.
|
||||
return { remote: local };
|
||||
}
|
||||
const baseToRemote = baseStorage ? compare(baseStorage, remoteStorage) : { added: Object.keys(remoteStorage).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
const baseToLocal = baseStorage ? compare(baseStorage, localStorage) : { added: Object.keys(localStorage).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
|
||||
const baseToLocal = base ? compare(base, local) : { added: Object.keys(local).reduce((r, k) => { r.add(k); return r; }, new Set<string>()), removed: new Set<string>(), updated: new Set<string>() };
|
||||
if (baseToLocal.added.size === 0 && baseToLocal.removed.size === 0 && baseToLocal.updated.size === 0) {
|
||||
// No changes found between base and local.
|
||||
return { local: remote };
|
||||
}
|
||||
|
||||
const merged = objects.deepClone(local);
|
||||
const local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> } = { added: {}, removed: [], updated: {} };
|
||||
const remote: IStringDictionary<IStorageValue> = objects.deepClone(remoteStorage);
|
||||
|
||||
// Added in remote
|
||||
for (const key of values(baseToRemote.added)) {
|
||||
merged[key] = remote[key];
|
||||
const remoteValue = remoteStorage[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in storage. It is not registered to sync.`);
|
||||
continue;
|
||||
}
|
||||
if (storageKey.version !== remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
continue;
|
||||
}
|
||||
const localValue = localStorage[key];
|
||||
if (localValue && localValue.value === remoteValue.value) {
|
||||
continue;
|
||||
}
|
||||
if (baseToLocal.added.has(key)) {
|
||||
local.updated[key] = remoteValue;
|
||||
} else {
|
||||
local.added[key] = remoteValue;
|
||||
}
|
||||
}
|
||||
|
||||
// Updated in Remote
|
||||
for (const key of values(baseToRemote.updated)) {
|
||||
merged[key] = remote[key];
|
||||
const remoteValue = remoteStorage[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in storage. It is not registered to sync.`);
|
||||
continue;
|
||||
}
|
||||
if (storageKey.version !== remoteValue.version) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in storage. Local version '${storageKey.version}' and remote version '${remoteValue.version} are not same.`);
|
||||
continue;
|
||||
}
|
||||
const localValue = localStorage[key];
|
||||
if (localValue && localValue.value === remoteValue.value) {
|
||||
continue;
|
||||
}
|
||||
local.updated[key] = remoteValue;
|
||||
}
|
||||
|
||||
// Removed in remote & local
|
||||
// Removed in remote
|
||||
for (const key of values(baseToRemote.removed)) {
|
||||
// Got removed in local
|
||||
if (baseToLocal.removed.has(key)) {
|
||||
delete merged[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (!storageKey) {
|
||||
logService.info(`GlobalState: Skipped updating ${key} in storage. It is not registered to sync.`);
|
||||
continue;
|
||||
}
|
||||
local.removed.push(key);
|
||||
}
|
||||
|
||||
// Added in local
|
||||
for (const key of values(baseToLocal.added)) {
|
||||
if (!baseToRemote.added.has(key)) {
|
||||
remote[key] = localStorage[key];
|
||||
}
|
||||
}
|
||||
|
||||
return { local: merged, remote: merged };
|
||||
// Updated in local
|
||||
for (const key of values(baseToLocal.updated)) {
|
||||
if (baseToRemote.updated.has(key) || baseToRemote.removed.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const remoteValue = remote[key];
|
||||
const localValue = localStorage[key];
|
||||
if (localValue.version < remoteValue.version) {
|
||||
continue;
|
||||
}
|
||||
remote[key] = localValue;
|
||||
}
|
||||
|
||||
// Removed in local
|
||||
for (const key of values(baseToLocal.removed)) {
|
||||
if (baseToRemote.updated.has(key)) {
|
||||
continue;
|
||||
}
|
||||
const remoteValue = remote[key];
|
||||
const storageKey = storageKeys.filter(storageKey => storageKey.key === key)[0];
|
||||
if (storageKey && storageKey.version < remoteValue.version) {
|
||||
continue;
|
||||
}
|
||||
delete remote[key];
|
||||
}
|
||||
|
||||
return { local, remote: areSame(remote, remoteStorage) ? null : remote };
|
||||
}
|
||||
|
||||
function compare(from: IStringDictionary<any>, to: IStringDictionary<any>): { added: Set<string>, removed: Set<string>, updated: Set<string> } {
|
||||
@@ -83,3 +140,9 @@ function compare(from: IStringDictionary<any>, to: IStringDictionary<any>): { ad
|
||||
|
||||
return { added, removed, updated };
|
||||
}
|
||||
|
||||
function areSame(a: IStringDictionary<IStorageValue>, b: IStringDictionary<IStorageValue>): boolean {
|
||||
const { added, removed, updated } = compare(a, b);
|
||||
return added.size === 0 && removed.size === 0 && updated.size === 0;
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { SyncStatus, IUserDataSyncStoreService, IUserDataSyncLogService, IGlobalState, SyncResource, IUserDataSynchroniser, IUserDataSyncEnablementService, IUserDataSyncBackupStoreService, ISyncResourceHandle, IStorageValue } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
@@ -19,12 +19,15 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
import { format } from 'vs/base/common/jsonFormatter';
|
||||
import { applyEdits } from 'vs/base/common/jsonEdit';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
const argvStoragePrefx = 'globalState.argv.';
|
||||
const argvProperties: string[] = ['locale'];
|
||||
|
||||
interface ISyncPreviewResult {
|
||||
readonly local: IGlobalState | undefined;
|
||||
readonly remote: IGlobalState | undefined;
|
||||
readonly local: { added: IStringDictionary<IStorageValue>, removed: string[], updated: IStringDictionary<IStorageValue> };
|
||||
readonly remote: IStringDictionary<IStorageValue> | null;
|
||||
readonly localUserData: IGlobalState;
|
||||
readonly remoteUserData: IRemoteUserData;
|
||||
readonly lastSyncUserData: IRemoteUserData | null;
|
||||
@@ -43,10 +46,13 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
@IUserDataSyncEnablementService userDataSyncEnablementService: IUserDataSyncEnablementService,
|
||||
@ITelemetryService telemetryService: ITelemetryService,
|
||||
@IConfigurationService configurationService: IConfigurationService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IStorageKeysSyncRegistryService private readonly storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super(SyncResource.GlobalState, fileService, environmentService, userDataSyncStoreService, userDataSyncBackupStoreService, userDataSyncEnablementService, telemetryService, logService, configurationService);
|
||||
this._register(this.fileService.watch(dirname(this.environmentService.argvResource)));
|
||||
this._register(Event.filter(this.fileService.onDidFilesChange, e => e.contains(this.environmentService.argvResource))(() => this._onDidChangeLocal.fire()));
|
||||
this._register(Event.filter(this.storageService.onDidChangeStorage, e => storageKeysSyncRegistryService.storageKeys.some(({ key }) => e.key === key))(() => this._onDidChangeLocal.fire()));
|
||||
}
|
||||
|
||||
async pull(): Promise<void> {
|
||||
@@ -65,9 +71,10 @@ 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, localUserData, lastSyncUserData });
|
||||
const localGlobalState = await this.getLocalGlobalState();
|
||||
const remoteGlobalState: IGlobalState = JSON.parse(remoteUserData.syncData.content);
|
||||
const { local, remote } = merge(localGlobalState.storage, remoteGlobalState.storage, null, this.getSyncStorageKeys(), this.logService);
|
||||
await this.apply({ local, remote, remoteUserData, localUserData: localGlobalState, lastSyncUserData });
|
||||
}
|
||||
|
||||
// No remote exists to pull
|
||||
@@ -96,7 +103,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
const localUserData = await this.getLocalGlobalState();
|
||||
const lastSyncUserData = await this.getLastSyncUserData();
|
||||
const remoteUserData = await this.getRemoteUserData(lastSyncUserData);
|
||||
await this.apply({ local: undefined, remote: localUserData, remoteUserData, localUserData, lastSyncUserData }, true);
|
||||
await this.apply({ local: { added: {}, removed: [], updated: {} }, remote: localUserData.storage, remoteUserData, localUserData, lastSyncUserData }, true);
|
||||
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Finished pushing UI State.`);
|
||||
} finally {
|
||||
@@ -136,8 +143,8 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
|
||||
async hasLocalData(): Promise<boolean> {
|
||||
try {
|
||||
const localGloablState = await this.getLocalGlobalState();
|
||||
if (localGloablState.argv['locale'] !== 'en') {
|
||||
const { storage } = await this.getLocalGlobalState();
|
||||
if (Object.keys(storage).length > 1 || storage[`${argvStoragePrefx}.locale`]?.value !== 'en') {
|
||||
return true;
|
||||
}
|
||||
} catch (error) {
|
||||
@@ -154,7 +161,7 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
|
||||
private async getPreview(remoteUserData: IRemoteUserData, lastSyncUserData: IRemoteUserData | null): Promise<ISyncPreviewResult> {
|
||||
const remoteGlobalState: IGlobalState = remoteUserData.syncData ? JSON.parse(remoteUserData.syncData.content) : null;
|
||||
const lastSyncGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
|
||||
const lastSyncGlobalState: IGlobalState = lastSyncUserData && lastSyncUserData.syncData ? JSON.parse(lastSyncUserData.syncData.content) : null;
|
||||
|
||||
const localGloablState = await this.getLocalGlobalState();
|
||||
|
||||
@@ -164,20 +171,21 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Remote ui state does not exist. Synchronizing ui state for the first time.`);
|
||||
}
|
||||
|
||||
const { local, remote } = merge(localGloablState, remoteGlobalState, lastSyncGlobalState);
|
||||
const { local, remote } = merge(localGloablState.storage, remoteGlobalState ? remoteGlobalState.storage : null, lastSyncGlobalState ? lastSyncGlobalState.storage : null, this.getSyncStorageKeys(), this.logService);
|
||||
|
||||
return { local, remote, remoteUserData, localUserData: localGloablState, lastSyncUserData };
|
||||
}
|
||||
|
||||
private async apply({ local, remote, remoteUserData, lastSyncUserData, localUserData }: ISyncPreviewResult, forcePush?: boolean): Promise<void> {
|
||||
|
||||
const hasChanges = local || remote;
|
||||
const hasLocalChanged = Object.keys(local.added).length > 0 || Object.keys(local.updated).length > 0 || local.removed.length > 0;
|
||||
const hasRemoteChanged = remote !== null;
|
||||
|
||||
if (!hasChanges) {
|
||||
if (!hasLocalChanged && !hasRemoteChanged) {
|
||||
this.logService.info(`${this.syncResourceLogLabel}: No changes found during synchronizing ui state.`);
|
||||
}
|
||||
|
||||
if (local) {
|
||||
if (hasLocalChanged) {
|
||||
// update local
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating local ui state...`);
|
||||
await this.backupLocal(JSON.stringify(localUserData));
|
||||
@@ -185,10 +193,10 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated local ui state`);
|
||||
}
|
||||
|
||||
if (remote) {
|
||||
if (hasRemoteChanged) {
|
||||
// update remote
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating remote ui state...`);
|
||||
const content = JSON.stringify(remote);
|
||||
const content = JSON.stringify(<IGlobalState>{ storage: remote });
|
||||
remoteUserData = await this.updateRemoteUserData(content, forcePush ? null : remoteUserData.ref);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated remote ui state`);
|
||||
}
|
||||
@@ -202,16 +210,21 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
}
|
||||
|
||||
private async getLocalGlobalState(): Promise<IGlobalState> {
|
||||
const argv: IStringDictionary<any> = {};
|
||||
const storage: IStringDictionary<any> = {};
|
||||
const storage: IStringDictionary<IStorageValue> = {};
|
||||
const argvContent: string = await this.getLocalArgvContent();
|
||||
const argvValue: IStringDictionary<any> = parse(argvContent);
|
||||
for (const argvProperty of argvProperties) {
|
||||
if (argvValue[argvProperty] !== undefined) {
|
||||
argv[argvProperty] = argvValue[argvProperty];
|
||||
storage[`${argvStoragePrefx}${argvProperty}`] = { version: 1, value: argvValue[argvProperty] };
|
||||
}
|
||||
}
|
||||
return { argv, storage };
|
||||
for (const { key, version } of this.storageKeysSyncRegistryService.storageKeys) {
|
||||
const value = this.storageService.get(key, StorageScope.GLOBAL);
|
||||
if (value) {
|
||||
storage[key] = { version, value };
|
||||
}
|
||||
}
|
||||
return { storage };
|
||||
}
|
||||
|
||||
private async getLocalArgvContent(): Promise<string> {
|
||||
@@ -222,15 +235,59 @@ export class GlobalStateSynchroniser extends AbstractSynchroniser implements IUs
|
||||
return '{}';
|
||||
}
|
||||
|
||||
private async writeLocalGlobalState(globalState: IGlobalState): Promise<void> {
|
||||
const argvContent = await this.getLocalArgvContent();
|
||||
let content = argvContent;
|
||||
for (const argvProperty of Object.keys(globalState.argv)) {
|
||||
content = edit(content, [argvProperty], globalState.argv[argvProperty], {});
|
||||
private async writeLocalGlobalState({ added, removed, updated }: { added: IStringDictionary<IStorageValue>, updated: IStringDictionary<IStorageValue>, removed: string[] }): Promise<void> {
|
||||
const argv: IStringDictionary<any> = {};
|
||||
const updatedStorage: IStringDictionary<any> = {};
|
||||
const handleUpdatedStorage = (keys: string[], storage?: IStringDictionary<IStorageValue>): void => {
|
||||
for (const key of keys) {
|
||||
if (key.startsWith(argvStoragePrefx)) {
|
||||
argv[key.substring(argvStoragePrefx.length)] = storage ? storage[key].value : undefined;
|
||||
continue;
|
||||
}
|
||||
if (storage) {
|
||||
const storageValue = storage[key];
|
||||
if (storageValue.value !== String(this.storageService.get(key, StorageScope.GLOBAL))) {
|
||||
updatedStorage[key] = storageValue.value;
|
||||
}
|
||||
} else {
|
||||
if (this.storageService.get(key, StorageScope.GLOBAL) !== undefined) {
|
||||
updatedStorage[key] = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
handleUpdatedStorage(Object.keys(added), added);
|
||||
handleUpdatedStorage(Object.keys(updated), updated);
|
||||
handleUpdatedStorage(removed);
|
||||
if (Object.keys(argv).length) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`);
|
||||
await this.updateArgv(argv);
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated locale`);
|
||||
}
|
||||
if (argvContent !== content) {
|
||||
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
|
||||
const updatedStorageKeys: string[] = Object.keys(updatedStorage);
|
||||
if (updatedStorageKeys.length) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating global state...`);
|
||||
for (const key of Object.keys(updatedStorage)) {
|
||||
this.storageService.store(key, updatedStorage[key], StorageScope.GLOBAL);
|
||||
}
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated global state`, Object.keys(updatedStorage));
|
||||
}
|
||||
}
|
||||
|
||||
private async updateArgv(argv: IStringDictionary<any>): Promise<void> {
|
||||
const argvContent = await this.getLocalArgvContent();
|
||||
let content = argvContent;
|
||||
for (const argvProperty of Object.keys(argv)) {
|
||||
content = edit(content, [argvProperty], argv[argvProperty], {});
|
||||
}
|
||||
if (argvContent !== content) {
|
||||
this.logService.trace(`${this.syncResourceLogLabel}: Updating locale...`);
|
||||
await this.fileService.writeFile(this.environmentService.argvResource, VSBuffer.fromString(content));
|
||||
this.logService.info(`${this.syncResourceLogLabel}: Updated locale.`);
|
||||
}
|
||||
}
|
||||
|
||||
private getSyncStorageKeys(): IStorageKey[] {
|
||||
return [...this.storageKeysSyncRegistryService.storageKeys, ...argvProperties.map(argvProprety => (<IStorageKey>{ key: `${argvStoragePrefx}${argvProprety}`, version: 1 }))];
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { Disposable, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
|
||||
export interface IStorageKey {
|
||||
|
||||
readonly key: string;
|
||||
readonly version: number;
|
||||
|
||||
}
|
||||
|
||||
export const IStorageKeysSyncRegistryService = createDecorator<IStorageKeysSyncRegistryService>('IStorageKeysSyncRegistryService');
|
||||
|
||||
export interface IStorageKeysSyncRegistryService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* All registered storage keys
|
||||
*/
|
||||
readonly storageKeys: ReadonlyArray<IStorageKey>;
|
||||
|
||||
/**
|
||||
* Event that is triggered when storage keys are changed
|
||||
*/
|
||||
readonly onDidChangeStorageKeys: Event<ReadonlyArray<IStorageKey>>;
|
||||
|
||||
/**
|
||||
* Register a storage key that has to be synchronized during sync.
|
||||
*/
|
||||
registerStorageKey(key: IStorageKey): void;
|
||||
|
||||
}
|
||||
|
||||
export class StorageKeysSyncRegistryService extends Disposable implements IStorageKeysSyncRegistryService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private readonly _storageKeys = new Map<string, IStorageKey>();
|
||||
get storageKeys(): ReadonlyArray<IStorageKey> { return values(this._storageKeys); }
|
||||
|
||||
private readonly _onDidChangeStorageKeys: Emitter<ReadonlyArray<IStorageKey>> = this._register(new Emitter<ReadonlyArray<IStorageKey>>());
|
||||
readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._register(toDisposable(() => this._storageKeys.clear()));
|
||||
}
|
||||
|
||||
registerStorageKey(storageKey: IStorageKey): void {
|
||||
if (!this._storageKeys.has(storageKey.key)) {
|
||||
this._storageKeys.set(storageKey.key, storageKey);
|
||||
this._onDidChangeStorageKeys.fire(this.storageKeys);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -125,7 +125,7 @@ export interface IUserDataSyncStore {
|
||||
}
|
||||
|
||||
export function getUserDataSyncStore(productService: IProductService, configurationService: IConfigurationService): IUserDataSyncStore | undefined {
|
||||
const value = productService[CONFIGURATION_SYNC_STORE_KEY] || configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY);
|
||||
const value = configurationService.getValue<{ url: string, authenticationProviderId: string }>(CONFIGURATION_SYNC_STORE_KEY) || productService[CONFIGURATION_SYNC_STORE_KEY];
|
||||
if (value && value.url && value.authenticationProviderId) {
|
||||
return {
|
||||
url: joinPath(URI.parse(value.url), 'v1'),
|
||||
@@ -231,9 +231,13 @@ export interface ISyncExtension {
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
export interface IStorageValue {
|
||||
version: number;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface IGlobalState {
|
||||
argv: IStringDictionary<any>;
|
||||
storage: IStringDictionary<any>;
|
||||
storage: IStringDictionary<IStorageValue>;
|
||||
}
|
||||
|
||||
export const enum SyncStatus {
|
||||
|
||||
@@ -4,11 +4,13 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { IServerChannel, IChannel } from 'vs/base/parts/ipc/common/ipc';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IUserDataSyncService, IUserDataSyncUtilService, 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';
|
||||
import { IStorageKeysSyncRegistryService, IStorageKey } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
|
||||
export class UserDataSyncChannel implements IServerChannel {
|
||||
|
||||
@@ -101,3 +103,51 @@ export class UserDataSyncUtilServiceClient implements IUserDataSyncUtilService {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export class StorageKeysSyncRegistryChannel implements IServerChannel {
|
||||
|
||||
constructor(private readonly service: IStorageKeysSyncRegistryService) { }
|
||||
|
||||
listen(_: unknown, event: string): Event<any> {
|
||||
switch (event) {
|
||||
case 'onDidChangeStorageKeys': return this.service.onDidChangeStorageKeys;
|
||||
}
|
||||
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.storageKeys);
|
||||
case 'registerStorageKey': return Promise.resolve(this.service.registerStorageKey(args[0]));
|
||||
}
|
||||
throw new Error('Invalid call');
|
||||
}
|
||||
}
|
||||
|
||||
export class StorageKeysSyncRegistryChannelClient extends Disposable implements IStorageKeysSyncRegistryService {
|
||||
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private _storageKeys: ReadonlyArray<IStorageKey> = [];
|
||||
get storageKeys(): ReadonlyArray<IStorageKey> { return this._storageKeys; }
|
||||
private readonly _onDidChangeStorageKeys: Emitter<ReadonlyArray<IStorageKey>> = this._register(new Emitter<ReadonlyArray<IStorageKey>>());
|
||||
readonly onDidChangeStorageKeys = this._onDidChangeStorageKeys.event;
|
||||
|
||||
constructor(private readonly channel: IChannel) {
|
||||
super();
|
||||
this.channel.call<IStorageKey[]>('_getInitialData').then(storageKeys => {
|
||||
this.updateStorageKeys(storageKeys);
|
||||
this._register(this.channel.listen<ReadonlyArray<IStorageKey>>('onDidChangeStorageKeys')(storageKeys => this.updateStorageKeys(storageKeys)));
|
||||
});
|
||||
}
|
||||
|
||||
private async updateStorageKeys(storageKeys: ReadonlyArray<IStorageKey>): Promise<void> {
|
||||
this._storageKeys = storageKeys;
|
||||
this._onDidChangeStorageKeys.fire(this.storageKeys);
|
||||
}
|
||||
|
||||
registerStorageKey(storageKey: IStorageKey): void {
|
||||
this.channel.call('registerStorageKey', [storageKey]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,367 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { merge } from 'vs/platform/userDataSync/common/globalStateMerge';
|
||||
import { NullLogService } from 'vs/platform/log/common/log';
|
||||
|
||||
suite('GlobalStateMerge', () => {
|
||||
|
||||
test('merge when local and remote are same with one value', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with multiple entries in different order', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote are same with different base content', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const base = { 'b': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to remote', async () => {
|
||||
const local = {};
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when new entry is added to remote from base and local has not changed', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'b': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from remote from base and local has not changed', async () => {
|
||||
const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, ['b']);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when all entries are removed from base and local has not changed', async () => {
|
||||
const local = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
const remote = {};
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, ['b', 'a']);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in remote from base and local has not changed', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when remote has moved forwarded with multiple changes and local stays with base', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'd' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } });
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'd' } });
|
||||
assert.deepEqual(actual.local.removed, ['b']);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when new entries are added to local', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when multiple new entries are added to local from base and remote is not changed', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' }, 'c': { version: 1, value: 'c' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when an entry is removed from local from base and remote has not changed', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated in local from base and remote has not changed', async () => {
|
||||
const local = { 'a': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local has moved forwarded with multiple changes and remote stays with base', async () => {
|
||||
const local = { 'a': { version: 1, value: 'd' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when local and remote with one entry but different value', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when the entry is removed in remote but updated in local and a new entry is added in remote', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'd' } };
|
||||
const remote = { 'a': { version: 1, value: 'a' }, 'c': { version: 1, value: 'c' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, { 'c': { version: 1, value: 'c' } });
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, ['b']);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge with single entry and local is empty', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' } };
|
||||
const local = {};
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when local and remote has moved forwareded with conflicts', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' } };
|
||||
const local = { 'a': { version: 1, value: 'd' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }, { key: 'c', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, { 'a': { version: 1, value: 'b' } });
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote but not a registered key', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is added to remote but different version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, null, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when an entry is updated to remote but not a registered key', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'a': { version: 1, value: 'b' } };
|
||||
|
||||
const actual = merge(local, remote, local, [], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a new entry is updated to remote but different version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, local, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is update with lower version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'c' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is update with higher version', async () => {
|
||||
const local = { 'a': { version: 1, value: 'a' }, 'b': { version: 2, value: 'c' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, remote, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed but not registered', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed with lower version', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 2, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 1 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, null);
|
||||
});
|
||||
|
||||
test('merge when a local value is removed with higher version', async () => {
|
||||
const base = { 'a': { version: 1, value: 'a' }, 'b': { version: 1, value: 'b' } };
|
||||
const local = { 'a': { version: 1, value: 'a' } };
|
||||
const remote = { 'b': { version: 1, value: 'b' }, 'a': { version: 1, value: 'a' } };
|
||||
|
||||
const actual = merge(local, remote, base, [{ key: 'a', version: 1 }, { key: 'b', version: 2 }], new NullLogService());
|
||||
|
||||
assert.deepEqual(actual.local.added, {});
|
||||
assert.deepEqual(actual.local.updated, {});
|
||||
assert.deepEqual(actual.local.removed, []);
|
||||
assert.deepEqual(actual.remote, local);
|
||||
});
|
||||
|
||||
});
|
||||
@@ -0,0 +1,224 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as assert from 'assert';
|
||||
import { IUserDataSyncStoreService, IUserDataSyncService, SyncResource, SyncStatus, IGlobalState } from 'vs/platform/userDataSync/common/userDataSync';
|
||||
import { UserDataSyncClient, UserDataSyncTestServer } from 'vs/platform/userDataSync/test/common/userDataSyncClient';
|
||||
import { DisposableStore, toDisposable } from 'vs/base/common/lifecycle';
|
||||
import { UserDataSyncService } from 'vs/platform/userDataSync/common/userDataSyncService';
|
||||
import { IFileService } from 'vs/platform/files/common/files';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { ISyncData } from 'vs/platform/userDataSync/common/abstractSynchronizer';
|
||||
import { IStorageService, StorageScope } from 'vs/platform/storage/common/storage';
|
||||
import { GlobalStateSynchroniser } from 'vs/platform/userDataSync/common/globalStateSync';
|
||||
import { VSBuffer } from 'vs/base/common/buffer';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
|
||||
suite('GlobalStateSync', () => {
|
||||
|
||||
const disposableStore = new DisposableStore();
|
||||
const server = new UserDataSyncTestServer();
|
||||
let testClient: UserDataSyncClient;
|
||||
let client2: UserDataSyncClient;
|
||||
|
||||
let testObject: GlobalStateSynchroniser;
|
||||
|
||||
setup(async () => {
|
||||
testClient = disposableStore.add(new UserDataSyncClient(server));
|
||||
await testClient.setUp(true);
|
||||
let storageKeysSyncRegistryService = testClient.instantiationService.get(IStorageKeysSyncRegistryService);
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 });
|
||||
testObject = (testClient.instantiationService.get(IUserDataSyncService) as UserDataSyncService).getSynchroniser(SyncResource.GlobalState) as GlobalStateSynchroniser;
|
||||
disposableStore.add(toDisposable(() => testClient.instantiationService.get(IUserDataSyncStoreService).clear()));
|
||||
|
||||
client2 = disposableStore.add(new UserDataSyncClient(server));
|
||||
await client2.setUp(true);
|
||||
storageKeysSyncRegistryService = client2.instantiationService.get(IStorageKeysSyncRegistryService);
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'a', version: 1 });
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: 'b', version: 1 });
|
||||
});
|
||||
|
||||
teardown(() => disposableStore.clear());
|
||||
|
||||
test('first time sync - outgoing to server (no state)', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
await updateLocale(testClient);
|
||||
|
||||
await testObject.sync();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'globalState.argv.locale': { version: 1, value: 'en' }, 'a': { version: 1, value: 'value1' } });
|
||||
});
|
||||
|
||||
test('first time sync - incoming from server (no state)', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
await updateLocale(client2);
|
||||
await client2.sync();
|
||||
|
||||
await testObject.sync();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value1');
|
||||
assert.equal(await readLocale(testClient), 'en');
|
||||
});
|
||||
|
||||
test('first time sync when storage exists', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
await client2.sync();
|
||||
|
||||
updateStorage('b', 'value2', testClient);
|
||||
await testObject.sync();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value1');
|
||||
assert.equal(readStorage('b', testClient), 'value2');
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } });
|
||||
});
|
||||
|
||||
test('first time sync when storage exists - has conflicts', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
await client2.sync();
|
||||
|
||||
updateStorage('a', 'value2', client2);
|
||||
await testObject.sync();
|
||||
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value1');
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } });
|
||||
});
|
||||
|
||||
test('sync adding a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
await testObject.sync();
|
||||
|
||||
updateStorage('b', 'value2', testClient);
|
||||
await testObject.sync();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value1');
|
||||
assert.equal(readStorage('b', testClient), 'value2');
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } });
|
||||
});
|
||||
|
||||
test('sync updating a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
await testObject.sync();
|
||||
|
||||
updateStorage('a', 'value2', testClient);
|
||||
await testObject.sync();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value2');
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value2' } });
|
||||
});
|
||||
|
||||
test('sync removing a storage value', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateStorage('b', 'value2', testClient);
|
||||
await testObject.sync();
|
||||
|
||||
removeStorage('b', testClient);
|
||||
await testObject.sync();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value1');
|
||||
assert.equal(readStorage('b', testClient), undefined);
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' } });
|
||||
});
|
||||
|
||||
test('first time sync - push', async () => {
|
||||
updateStorage('a', 'value1', testClient);
|
||||
updateStorage('b', 'value2', testClient);
|
||||
|
||||
await testObject.push();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
const { content } = await testClient.read(testObject.resource);
|
||||
assert.ok(content !== null);
|
||||
const actual = parseGlobalState(content!);
|
||||
assert.deepEqual(actual.storage, { 'a': { version: 1, value: 'value1' }, 'b': { version: 1, value: 'value2' } });
|
||||
});
|
||||
|
||||
test('first time sync - pull', async () => {
|
||||
updateStorage('a', 'value1', client2);
|
||||
updateStorage('b', 'value2', client2);
|
||||
await client2.sync();
|
||||
|
||||
await testObject.pull();
|
||||
assert.equal(testObject.status, SyncStatus.Idle);
|
||||
assert.deepEqual(testObject.conflicts, []);
|
||||
|
||||
assert.equal(readStorage('a', testClient), 'value1');
|
||||
assert.equal(readStorage('b', testClient), 'value2');
|
||||
});
|
||||
|
||||
function parseGlobalState(content: string): IGlobalState {
|
||||
const syncData: ISyncData = JSON.parse(content);
|
||||
return JSON.parse(syncData.content);
|
||||
}
|
||||
|
||||
async function updateLocale(client: UserDataSyncClient): Promise<void> {
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const environmentService = client.instantiationService.get(IEnvironmentService);
|
||||
await fileService.writeFile(environmentService.argvResource, VSBuffer.fromString(JSON.stringify({ 'locale': 'en' })));
|
||||
}
|
||||
|
||||
function updateStorage(key: string, value: string, client: UserDataSyncClient): void {
|
||||
const storageService = client.instantiationService.get(IStorageService);
|
||||
storageService.store(key, value, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
function removeStorage(key: string, client: UserDataSyncClient): void {
|
||||
const storageService = client.instantiationService.get(IStorageService);
|
||||
storageService.remove(key, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
function readStorage(key: string, client: UserDataSyncClient): string | undefined {
|
||||
const storageService = client.instantiationService.get(IStorageService);
|
||||
return storageService.get(key, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
async function readLocale(client: UserDataSyncClient): Promise<string | undefined> {
|
||||
const fileService = client.instantiationService.get(IFileService);
|
||||
const environmentService = client.instantiationService.get(IEnvironmentService);
|
||||
const content = await fileService.readFile(environmentService.argvResource);
|
||||
return JSON.parse(content.value.toString()).locale;
|
||||
}
|
||||
|
||||
});
|
||||
@@ -68,7 +68,7 @@ suite('TestSynchronizer', () => {
|
||||
teardown(() => disposableStore.clear());
|
||||
|
||||
test('status is syncing', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
|
||||
const actual: SyncStatus[] = [];
|
||||
disposableStore.add(testObject.onDidChangeStatus(status => actual.push(status)));
|
||||
@@ -85,7 +85,7 @@ suite('TestSynchronizer', () => {
|
||||
});
|
||||
|
||||
test('status is set correctly when sync is finished', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
testObject.syncBarrier.open();
|
||||
|
||||
const actual: SyncStatus[] = [];
|
||||
@@ -97,7 +97,7 @@ suite('TestSynchronizer', () => {
|
||||
});
|
||||
|
||||
test('status is set correctly when sync has conflicts', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
testObject.syncResult = { status: SyncStatus.HasConflicts };
|
||||
testObject.syncBarrier.open();
|
||||
|
||||
@@ -110,7 +110,7 @@ suite('TestSynchronizer', () => {
|
||||
});
|
||||
|
||||
test('status is set correctly when sync has errors', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
testObject.syncResult = { error: true };
|
||||
testObject.syncBarrier.open();
|
||||
|
||||
@@ -127,7 +127,7 @@ suite('TestSynchronizer', () => {
|
||||
});
|
||||
|
||||
test('sync should not run if syncing already', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
const promise = Event.toPromise(testObject.onDoSyncCall.event);
|
||||
|
||||
testObject.sync();
|
||||
@@ -144,7 +144,7 @@ suite('TestSynchronizer', () => {
|
||||
});
|
||||
|
||||
test('sync should not run if disabled', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
client.instantiationService.get(IUserDataSyncEnablementService).setResourceEnablement(testObject.resource, false);
|
||||
|
||||
const actual: SyncStatus[] = [];
|
||||
@@ -157,7 +157,7 @@ suite('TestSynchronizer', () => {
|
||||
});
|
||||
|
||||
test('sync should not run if there are conflicts', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
testObject.syncResult = { status: SyncStatus.HasConflicts };
|
||||
testObject.syncBarrier.open();
|
||||
await testObject.sync();
|
||||
@@ -171,7 +171,7 @@ suite('TestSynchronizer', () => {
|
||||
});
|
||||
|
||||
test('request latest data on precondition failure', async () => {
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings, 'settings');
|
||||
const testObject: TestSynchroniser = client.instantiationService.createInstance(TestSynchroniser, SyncResource.Settings);
|
||||
// Sync once
|
||||
testObject.syncBarrier.open();
|
||||
await testObject.sync();
|
||||
|
||||
@@ -36,6 +36,7 @@ import { IAuthenticationTokenService } from 'vs/platform/authentication/common/a
|
||||
import product from 'vs/platform/product/common/product';
|
||||
import { IProductService } from 'vs/platform/product/common/productService';
|
||||
import { UserDataSyncBackupStoreService } from 'vs/platform/userDataSync/common/userDataSyncBackupStoreService';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
export class UserDataSyncClient extends Disposable {
|
||||
|
||||
@@ -92,6 +93,7 @@ export class UserDataSyncClient extends Disposable {
|
||||
this.instantiationService.stub(IUserDataSyncBackupStoreService, this.instantiationService.createInstance(UserDataSyncBackupStoreService));
|
||||
this.instantiationService.stub(IUserDataSyncUtilService, new TestUserDataSyncUtilService());
|
||||
this.instantiationService.stub(IUserDataSyncEnablementService, this.instantiationService.createInstance(UserDataSyncEnablementService));
|
||||
this.instantiationService.stub(IStorageKeysSyncRegistryService, this.instantiationService.createInstance(StorageKeysSyncRegistryService));
|
||||
|
||||
this.instantiationService.stub(IGlobalExtensionEnablementService, this.instantiationService.createInstance(GlobalExtensionEnablementService));
|
||||
this.instantiationService.stub(IExtensionManagementService, <Partial<IExtensionManagementService>>{
|
||||
|
||||
@@ -234,7 +234,6 @@ suite.skip('UserDataSyncService', () => { // {{SQL CARBON EDIT}} skip failing te
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/snippets`, headers: { 'If-Match': '1' } },
|
||||
// Global state
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/globalState/latest`, headers: {} },
|
||||
{ type: 'POST', url: `${target.url}/v1/resource/globalState`, headers: { 'If-Match': '1' } },
|
||||
// Extensions
|
||||
{ type: 'GET', url: `${target.url}/v1/resource/extensions/latest`, headers: {} },
|
||||
]);
|
||||
|
||||
Vendored
+1
-1
@@ -2100,7 +2100,7 @@ declare module 'vscode' {
|
||||
* @param pathFragments
|
||||
* @returns A new uri
|
||||
*/
|
||||
export function joinPaths(base: Uri, ...pathFragments: string[]): Uri;
|
||||
export function joinPath(base: Uri, ...pathFragments: string[]): Uri;
|
||||
}
|
||||
|
||||
//#endregion
|
||||
|
||||
@@ -452,6 +452,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
|
||||
id: COMMENTS_VIEW_ID,
|
||||
name: COMMENTS_VIEW_TITLE,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [COMMENTS_VIEW_ID, COMMENTS_VIEW_TITLE, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
|
||||
hideIfEmpty: true,
|
||||
order: 10,
|
||||
}, ViewContainerLocation.Panel);
|
||||
|
||||
@@ -460,6 +461,7 @@ export class MainThreadComments extends Disposable implements MainThreadComments
|
||||
name: COMMENTS_VIEW_TITLE,
|
||||
canToggleVisibility: false,
|
||||
ctorDescriptor: new SyncDescriptor(CommentsPanel),
|
||||
canMoveView: true,
|
||||
focusCommand: {
|
||||
id: 'workbench.action.focusCommentsPanel'
|
||||
}
|
||||
|
||||
@@ -199,6 +199,14 @@ export class MainThreadNotebookController implements IMainNotebookController {
|
||||
let notebookHandle = await this._mainThreadNotebook.resolveNotebook(viewType, uri);
|
||||
if (notebookHandle !== undefined) {
|
||||
mainthreadNotebook = this._mapping.get(URI.from(uri).toString());
|
||||
if (mainthreadNotebook && mainthreadNotebook.textModel.cells.length === 0) {
|
||||
// it's empty, we should create an empty template one
|
||||
const templateCell = await this._proxy.$createEmptyCell(this._viewType, uri, 0, mainthreadNotebook.textModel.languages.length ? mainthreadNotebook.textModel.languages[0] : '', CellKind.Code);
|
||||
if (templateCell) {
|
||||
let mainCell = new NotebookCellTextModel(URI.revive(templateCell.uri), templateCell.handle, templateCell.source, templateCell.language, templateCell.cellKind, templateCell.outputs, templateCell.metadata);
|
||||
mainthreadNotebook.textModel.insertTemplateCell(mainCell);
|
||||
}
|
||||
}
|
||||
return mainthreadNotebook?.textModel;
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,11 @@ export const viewsContainersContribution: IJSONSchema = {
|
||||
description: localize('views.container.activitybar', "Contribute views containers to Activity Bar"),
|
||||
type: 'array',
|
||||
items: viewsContainerSchema
|
||||
},
|
||||
'panel': {
|
||||
description: localize('views.container.panel', "Contribute views containers to Panel"),
|
||||
type: 'array',
|
||||
items: viewsContainerSchema
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -214,7 +219,8 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
|
||||
private addCustomViewContainers(extensionPoints: readonly IExtensionPointUser<ViewContainerExtensionPointType>[], existingViewContainers: ViewContainer[]): void {
|
||||
const viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
let order = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId).length + 1;
|
||||
let activityBarOrder = TEST_VIEW_CONTAINER_ORDER + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Sidebar).length + 1;
|
||||
let panelOrder = 5 + viewContainersRegistry.all.filter(v => !!v.extensionId && viewContainersRegistry.getViewContainerLocation(v) === ViewContainerLocation.Panel).length + 1;
|
||||
for (let { value, collector, description } of extensionPoints) {
|
||||
forEach(value, entry => {
|
||||
if (!this.isValidViewsContainer(entry.value, collector)) {
|
||||
@@ -222,7 +228,10 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
}
|
||||
switch (entry.key) {
|
||||
case 'activitybar':
|
||||
order = this.registerCustomViewContainers(entry.value, description, order, existingViewContainers);
|
||||
activityBarOrder = this.registerCustomViewContainers(entry.value, description, activityBarOrder, existingViewContainers, ViewContainerLocation.Sidebar);
|
||||
break;
|
||||
case 'panel':
|
||||
panelOrder = this.registerCustomViewContainers(entry.value, description, panelOrder, existingViewContainers, ViewContainerLocation.Panel);
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -248,7 +257,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
const title = localize('test', "Test");
|
||||
const icon = URI.parse(require.toUrl('./media/test.svg'));
|
||||
|
||||
this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined);
|
||||
this.registerCustomViewContainer(TEST_VIEW_CONTAINER_ID, title, icon, TEST_VIEW_CONTAINER_ORDER, undefined, ViewContainerLocation.Sidebar);
|
||||
}
|
||||
|
||||
private isValidViewsContainer(viewsContainersDescriptors: IUserFriendlyViewsContainerDescriptor[], collector: ExtensionMessageCollector): boolean {
|
||||
@@ -279,11 +288,11 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
return true;
|
||||
}
|
||||
|
||||
private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription, order: number, existingViewContainers: ViewContainer[]): number {
|
||||
private registerCustomViewContainers(containers: IUserFriendlyViewsContainerDescriptor[], extension: IExtensionDescription, order: number, existingViewContainers: ViewContainer[], location: ViewContainerLocation): number {
|
||||
containers.forEach(descriptor => {
|
||||
const icon = resources.joinPath(extension.extensionLocation, descriptor.icon);
|
||||
const id = `workbench.view.extension.${descriptor.id}`;
|
||||
const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier);
|
||||
const viewContainer = this.registerCustomViewContainer(id, descriptor.title, icon, order++, extension.identifier, location);
|
||||
|
||||
// Move those views that belongs to this container
|
||||
if (existingViewContainers.length) {
|
||||
@@ -301,7 +310,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
return order;
|
||||
}
|
||||
|
||||
private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, extensionId: ExtensionIdentifier | undefined): ViewContainer {
|
||||
private registerCustomViewContainer(id: string, title: string, icon: URI, order: number, extensionId: ExtensionIdentifier | undefined, location: ViewContainerLocation): ViewContainer {
|
||||
let viewContainer = this.viewContainersRegistry.get(id);
|
||||
|
||||
if (!viewContainer) {
|
||||
@@ -316,7 +325,7 @@ class ViewsExtensionHandler implements IWorkbenchContribution {
|
||||
hideIfEmpty: true,
|
||||
order,
|
||||
icon,
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
}, location);
|
||||
|
||||
// Register Action to Open Viewlet
|
||||
class OpenCustomViewletAction extends ShowViewletAction {
|
||||
|
||||
@@ -24,7 +24,7 @@ import { InEditorZenModeContext, IsCenteredLayoutContext, EditorAreaVisibleConte
|
||||
import { ContextKeyExpr, IContextKeyService } 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, IViewsService, FocusedViewContext, ViewContainerLocation } from 'vs/workbench/common/views';
|
||||
import { IViewDescriptorService, IViewContainersRegistry, Extensions as ViewContainerExtensions, IViewsService, FocusedViewContext, ViewContainerLocation, IViewDescriptor } from 'vs/workbench/common/views';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||
@@ -598,6 +598,49 @@ export class MoveFocusedViewAction extends Action {
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(MoveFocusedViewAction, MoveFocusedViewAction.ID, MoveFocusedViewAction.LABEL), 'View: Move Focused View', viewCategory, FocusedViewContext.notEqualsTo(''));
|
||||
|
||||
|
||||
// --- Reset View Location with Command
|
||||
export class ResetFocusedViewLocationAction extends Action {
|
||||
static readonly ID = 'workbench.action.resetFocusedViewLocation';
|
||||
static readonly LABEL = nls.localize('resetFocusedViewLocation', "Reset Focused View Location");
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
label: string,
|
||||
@IViewDescriptorService private viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@INotificationService private notificationService: INotificationService,
|
||||
@IViewsService private viewsService: IViewsService
|
||||
) {
|
||||
super(id, label);
|
||||
}
|
||||
|
||||
async run(): Promise<void> {
|
||||
const focusedViewId = FocusedViewContext.getValue(this.contextKeyService);
|
||||
|
||||
let viewDescriptor: IViewDescriptor | null = null;
|
||||
if (focusedViewId !== undefined && focusedViewId.trim() !== '') {
|
||||
viewDescriptor = this.viewDescriptorService.getViewDescriptor(focusedViewId);
|
||||
}
|
||||
|
||||
if (!viewDescriptor) {
|
||||
this.notificationService.error(nls.localize('resetFocusedView.error.noFocusedView', "There is no view currently focused."));
|
||||
return;
|
||||
}
|
||||
|
||||
const defaultContainer = this.viewDescriptorService.getDefaultContainer(viewDescriptor.id);
|
||||
if (!defaultContainer || defaultContainer === this.viewDescriptorService.getViewContainer(viewDescriptor.id)) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.viewDescriptorService.moveViewsToContainer([viewDescriptor], defaultContainer);
|
||||
this.viewsService.openView(viewDescriptor.id, true);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(ResetFocusedViewLocationAction, ResetFocusedViewLocationAction.ID, ResetFocusedViewLocationAction.LABEL), 'View: Reset Focused View Location', viewCategory, FocusedViewContext.notEqualsTo(''));
|
||||
|
||||
|
||||
// --- Resize View
|
||||
|
||||
export abstract class BaseResizeViewAction extends Action {
|
||||
|
||||
@@ -39,6 +39,21 @@ import { IConfigurationService } from 'vs/platform/configuration/common/configur
|
||||
import { getMenuBarVisibility } from 'vs/platform/windows/common/windows';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
interface IPlaceholderViewlet {
|
||||
id: string;
|
||||
name?: string;
|
||||
iconUrl?: UriComponents;
|
||||
views?: { when?: string }[];
|
||||
}
|
||||
|
||||
interface IPinnedViewlet {
|
||||
id: string;
|
||||
pinned: boolean;
|
||||
order?: number;
|
||||
visible: boolean;
|
||||
}
|
||||
|
||||
interface ICachedViewlet {
|
||||
id: string;
|
||||
@@ -55,7 +70,8 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
_serviceBrand: undefined;
|
||||
|
||||
private static readonly ACTION_HEIGHT = 48;
|
||||
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets';
|
||||
private static readonly PINNED_VIEWLETS = 'workbench.activity.pinnedViewlets2';
|
||||
private static readonly PLACEHOLDER_VIEWLETS = 'workbench.activity.placeholderViewlets';
|
||||
|
||||
//#region IView
|
||||
|
||||
@@ -92,9 +108,12 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||
@IWorkbenchEnvironmentService workbenchEnvironmentService: IWorkbenchEnvironmentService,
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService
|
||||
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
) {
|
||||
super(Parts.ACTIVITYBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
|
||||
this.migrateFromOldCachedViewletsValue();
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: ActivitybarPart.PINNED_VIEWLETS, version: 1 });
|
||||
|
||||
this.cachedViewlets = this.getCachedViewlets();
|
||||
for (const cachedViewlet of this.cachedViewlets) {
|
||||
@@ -528,10 +547,15 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
this.compositeBar.layout(new Dimension(width, availableHeight));
|
||||
}
|
||||
|
||||
private getViewContainer(viewletId: string): ViewContainer | undefined {
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
return viewContainerRegistry.get(viewletId);
|
||||
}
|
||||
|
||||
private onDidStorageChange(e: IWorkspaceStorageChangeEvent): void {
|
||||
if (e.key === ActivitybarPart.PINNED_VIEWLETS && e.scope === StorageScope.GLOBAL
|
||||
&& this.cachedViewletsValue !== this.getStoredCachedViewletsValue() /* This checks if current window changed the value or not */) {
|
||||
this._cachedViewletsValue = undefined;
|
||||
&& this.pinnedViewletsValue !== this.getStoredPinnedViewletsValue() /* This checks if current window changed the value or not */) {
|
||||
this._pinnedViewletsValue = undefined;
|
||||
const newCompositeItems: ICompositeBarItem[] = [];
|
||||
const compositeItems = this.compositeBar.getCompositeBarItems();
|
||||
const cachedViewlets = this.getCachedViewlets();
|
||||
@@ -583,64 +607,88 @@ export class ActivitybarPart extends Part implements IActivityBarService {
|
||||
}
|
||||
}
|
||||
|
||||
this.cachedViewletsValue = JSON.stringify(state);
|
||||
this.storeCachedViewletsState(state);
|
||||
}
|
||||
|
||||
private getCachedViewlets(): ICachedViewlet[] {
|
||||
const storedStates: Array<string | ICachedViewlet> = JSON.parse(this.cachedViewletsValue);
|
||||
const cachedViewlets = storedStates.map(c => {
|
||||
const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, iconUrl: undefined, views: undefined } : c;
|
||||
serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible;
|
||||
return serialized;
|
||||
});
|
||||
|
||||
for (const old of this.loadOldCachedViewlets()) {
|
||||
const cachedViewlet = cachedViewlets.filter(cached => cached.id === old.id)[0];
|
||||
const cachedViewlets: Array<ICachedViewlet> = JSON.parse(this.pinnedViewletsValue);
|
||||
for (const placeholderViewlet of JSON.parse(this.placeholderViewletsValue)) {
|
||||
const cachedViewlet = cachedViewlets.filter(cached => cached.id === placeholderViewlet.id)[0];
|
||||
if (cachedViewlet) {
|
||||
cachedViewlet.name = old.name;
|
||||
cachedViewlet.iconUrl = old.iconUrl;
|
||||
cachedViewlet.views = old.views;
|
||||
cachedViewlet.name = placeholderViewlet.name;
|
||||
cachedViewlet.iconUrl = placeholderViewlet.iconUrl;
|
||||
cachedViewlet.views = placeholderViewlet.views;
|
||||
}
|
||||
}
|
||||
|
||||
return cachedViewlets;
|
||||
}
|
||||
|
||||
private loadOldCachedViewlets(): ICachedViewlet[] {
|
||||
const previousState = this.storageService.get('workbench.activity.placeholderViewlets', StorageScope.GLOBAL, '[]');
|
||||
const result: ICachedViewlet[] = JSON.parse(previousState);
|
||||
this.storageService.remove('workbench.activity.placeholderViewlets', StorageScope.GLOBAL);
|
||||
|
||||
return result;
|
||||
private storeCachedViewletsState(cachedViewlets: ICachedViewlet[]): void {
|
||||
this.pinnedViewletsValue = JSON.stringify(cachedViewlets.map(({ id, pinned, visible, order }) => (<IPinnedViewlet>{ id, pinned, visible, order })));
|
||||
this.placeholderViewletsValue = JSON.stringify(cachedViewlets.map(({ id, iconUrl, name, views }) => (<IPlaceholderViewlet>{ id, iconUrl, name, views })));
|
||||
}
|
||||
|
||||
private _cachedViewletsValue: string | undefined;
|
||||
private get cachedViewletsValue(): string {
|
||||
if (!this._cachedViewletsValue) {
|
||||
this._cachedViewletsValue = this.getStoredCachedViewletsValue();
|
||||
private _pinnedViewletsValue: string | undefined;
|
||||
private get pinnedViewletsValue(): string {
|
||||
if (!this._pinnedViewletsValue) {
|
||||
this._pinnedViewletsValue = this.getStoredPinnedViewletsValue();
|
||||
}
|
||||
|
||||
return this._cachedViewletsValue;
|
||||
return this._pinnedViewletsValue;
|
||||
}
|
||||
|
||||
private set cachedViewletsValue(cachedViewletsValue: string) {
|
||||
if (this.cachedViewletsValue !== cachedViewletsValue) {
|
||||
this._cachedViewletsValue = cachedViewletsValue;
|
||||
this.setStoredCachedViewletsValue(cachedViewletsValue);
|
||||
private set pinnedViewletsValue(pinnedViewletsValue: string) {
|
||||
if (this.pinnedViewletsValue !== pinnedViewletsValue) {
|
||||
this._pinnedViewletsValue = pinnedViewletsValue;
|
||||
this.setStoredPinnedViewletsValue(pinnedViewletsValue);
|
||||
}
|
||||
}
|
||||
|
||||
private getStoredCachedViewletsValue(): string {
|
||||
private getStoredPinnedViewletsValue(): string {
|
||||
return this.storageService.get(ActivitybarPart.PINNED_VIEWLETS, StorageScope.GLOBAL, '[]');
|
||||
}
|
||||
|
||||
private setStoredCachedViewletsValue(value: string): void {
|
||||
private setStoredPinnedViewletsValue(value: string): void {
|
||||
this.storageService.store(ActivitybarPart.PINNED_VIEWLETS, value, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private getViewContainer(viewletId: string): ViewContainer | undefined {
|
||||
const viewContainerRegistry = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry);
|
||||
return viewContainerRegistry.get(viewletId);
|
||||
private _placeholderViewletsValue: string | undefined;
|
||||
private get placeholderViewletsValue(): string {
|
||||
if (!this._placeholderViewletsValue) {
|
||||
this._placeholderViewletsValue = this.getStoredPlaceholderViewletsValue();
|
||||
}
|
||||
|
||||
return this._placeholderViewletsValue;
|
||||
}
|
||||
|
||||
private set placeholderViewletsValue(placeholderViewletsValue: string) {
|
||||
if (this.placeholderViewletsValue !== placeholderViewletsValue) {
|
||||
this._placeholderViewletsValue = placeholderViewletsValue;
|
||||
this.setStoredPlaceholderViewletsValue(placeholderViewletsValue);
|
||||
}
|
||||
}
|
||||
|
||||
private getStoredPlaceholderViewletsValue(): string {
|
||||
return this.storageService.get(ActivitybarPart.PLACEHOLDER_VIEWLETS, StorageScope.GLOBAL, '[]');
|
||||
}
|
||||
|
||||
private setStoredPlaceholderViewletsValue(value: string): void {
|
||||
this.storageService.store(ActivitybarPart.PLACEHOLDER_VIEWLETS, value, StorageScope.GLOBAL);
|
||||
}
|
||||
|
||||
private migrateFromOldCachedViewletsValue(): void {
|
||||
const value = this.storageService.get('workbench.activity.pinnedViewlets', StorageScope.GLOBAL);
|
||||
if (value !== undefined) {
|
||||
const storedStates: Array<string | ICachedViewlet> = JSON.parse(value);
|
||||
const cachedViewlets = storedStates.map(c => {
|
||||
const serialized: ICachedViewlet = typeof c === 'string' /* migration from pinned states to composites states */ ? { id: c, pinned: true, order: undefined, visible: true, name: undefined, iconUrl: undefined, views: undefined } : c;
|
||||
serialized.visible = isUndefinedOrNull(serialized.visible) ? true : serialized.visible;
|
||||
return serialized;
|
||||
});
|
||||
this.storeCachedViewletsState(cachedViewlets);
|
||||
this.storageService.remove('workbench.activity.pinnedViewlets', StorageScope.GLOBAL);
|
||||
}
|
||||
}
|
||||
|
||||
toJSON(): object {
|
||||
|
||||
@@ -133,9 +133,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
}
|
||||
|
||||
// ... single view
|
||||
const defaultContainer = this.viewDescriptorService.getDefaultContainer(draggedViews[0].id);
|
||||
const canMoveToDefault = !!defaultContainer && this.viewDescriptorService.getViewContainerLocation(defaultContainer) === this.targetContainerLocation;
|
||||
return !!draggedViews[0].canMoveView && (!!draggedViews[0].containerIcon || canMoveToDefault || this.targetContainerLocation === ViewContainerLocation.Panel);
|
||||
return !!draggedViews[0].canMoveView;
|
||||
} else {
|
||||
// Dragging an individual view
|
||||
const viewDescriptor = this.viewDescriptorService.getViewDescriptor(dragData.id);
|
||||
@@ -146,7 +144,7 @@ export class CompositeDragAndDrop implements ICompositeDragAndDrop {
|
||||
}
|
||||
|
||||
// ... to create a view container
|
||||
return this.targetContainerLocation === ViewContainerLocation.Panel || !!viewDescriptor.containerIcon;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,9 @@ import {
|
||||
SplitEditorUpAction, SplitEditorDownAction, MoveEditorToLeftGroupAction, MoveEditorToRightGroupAction, MoveEditorToAboveGroupAction, MoveEditorToBelowGroupAction, CloseAllEditorGroupsAction,
|
||||
JoinAllGroupsAction, FocusLeftGroup, FocusAboveGroup, FocusRightGroup, FocusBelowGroup, EditorLayoutSingleAction, EditorLayoutTwoColumnsAction, EditorLayoutThreeColumnsAction, EditorLayoutTwoByTwoGridAction,
|
||||
EditorLayoutTwoRowsAction, EditorLayoutThreeRowsAction, EditorLayoutTwoColumnsBottomAction, EditorLayoutTwoRowsRightAction, NewEditorGroupLeftAction, NewEditorGroupRightAction,
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction, QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickOpenNextRecentlyUsedEditorAction as QuickOpenLeastRecentlyUsedEditorAction, QuickOpenLeastRecentlyUsedEditorInGroupAction
|
||||
NewEditorGroupAboveAction, NewEditorGroupBelowAction, SplitEditorOrthogonalAction, CloseEditorInAllGroupsAction, NavigateToLastEditLocationAction, ToggleGroupSizesAction, ShowAllEditorsByMostRecentlyUsedAction,
|
||||
QuickOpenPreviousRecentlyUsedEditorAction, OpenPreviousRecentlyUsedEditorInGroupAction, OpenNextRecentlyUsedEditorInGroupAction, QuickOpenNextRecentlyUsedEditorAction as QuickOpenLeastRecentlyUsedEditorAction,
|
||||
QuickOpenLeastRecentlyUsedEditorInGroupAction
|
||||
} from 'vs/workbench/browser/parts/editor/editorActions';
|
||||
import * as editorCommands from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||
import { IEditorService } from 'vs/workbench/services/editor/common/editorService';
|
||||
|
||||
@@ -24,6 +24,7 @@ import { IWorkspacesService } from 'vs/platform/workspaces/common/workspaces';
|
||||
import { IFileDialogService, ConfirmResult } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { IWorkingCopyService } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { values } from 'vs/base/common/map';
|
||||
import { ItemActivation } from 'vs/platform/quickinput/common/quickInput';
|
||||
|
||||
export class ExecuteCommandAction extends Action {
|
||||
|
||||
@@ -1261,6 +1262,7 @@ export class BaseQuickOpenEditorAction extends Action {
|
||||
id: string,
|
||||
label: string,
|
||||
private prefix: string,
|
||||
private itemActivation: ItemActivation | undefined,
|
||||
@IQuickOpenService private readonly quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService private readonly keybindingService: IKeybindingService
|
||||
) {
|
||||
@@ -1270,7 +1272,10 @@ export class BaseQuickOpenEditorAction extends Action {
|
||||
async run(): Promise<void> {
|
||||
const keybindings = this.keybindingService.lookupKeybindings(this.id);
|
||||
|
||||
this.quickOpenService.show(this.prefix, { quickNavigateConfiguration: { keybindings } });
|
||||
this.quickOpenService.show(this.prefix, {
|
||||
quickNavigateConfiguration: { keybindings },
|
||||
autoFocus: this.itemActivation === ItemActivation.LAST ? { autoFocusLastEntry: true } : undefined
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1285,7 +1290,7 @@ export class QuickOpenPreviousRecentlyUsedEditorAction extends BaseQuickOpenEdit
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService);
|
||||
super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, undefined, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1300,7 +1305,7 @@ export class QuickOpenNextRecentlyUsedEditorAction extends BaseQuickOpenEditorAc
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService);
|
||||
super(id, label, NAVIGATE_ALL_EDITORS_BY_MOST_RECENTLY_USED_PREFIX, undefined, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1315,7 +1320,7 @@ export class QuickOpenPreviousRecentlyUsedEditorInGroupAction extends BaseQuickO
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService);
|
||||
super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, undefined, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1330,7 +1335,7 @@ export class QuickOpenLeastRecentlyUsedEditorInGroupAction extends BaseQuickOpen
|
||||
@IQuickOpenService quickOpenService: IQuickOpenService,
|
||||
@IKeybindingService keybindingService: IKeybindingService
|
||||
) {
|
||||
super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, quickOpenService, keybindingService);
|
||||
super(id, label, NAVIGATE_IN_ACTIVE_GROUP_BY_MOST_RECENTLY_USED_PREFIX, ItemActivation.LAST, quickOpenService, keybindingService);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -37,6 +37,7 @@ import { ViewContainer, IViewContainersRegistry, Extensions as ViewContainerExte
|
||||
import { MenuId } from 'vs/platform/actions/common/actions';
|
||||
import { ViewMenuActions } from 'vs/workbench/browser/parts/views/viewMenuActions';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
interface ICachedPanel {
|
||||
id: string;
|
||||
@@ -104,6 +105,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
@IViewDescriptorService private readonly viewDescriptorService: IViewDescriptorService,
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
) {
|
||||
super(
|
||||
notificationService,
|
||||
@@ -125,6 +127,7 @@ export class PanelPart extends CompositePart<Panel> implements IPanelService {
|
||||
);
|
||||
|
||||
this.panelRegistry = Registry.as<PanelRegistry>(PanelExtensions.Panels);
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: PanelPart.PINNED_PANELS, version: 1 });
|
||||
|
||||
this.compositeBar = this._register(this.instantiationService.createInstance(CompositeBar, this.getCachedPanels(), {
|
||||
icon: false,
|
||||
|
||||
@@ -47,7 +47,7 @@ import { IEditorService, ACTIVE_GROUP, SIDE_GROUP } from 'vs/workbench/services/
|
||||
import { IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { timeout } from 'vs/base/common/async';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IQuickInputService, IQuickPickItem, ItemActivation } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { CancellationTokenSource, CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { IStorageService } from 'vs/platform/storage/common/storage';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
@@ -179,7 +179,20 @@ export class QuickOpenController extends Component implements IQuickOpenService
|
||||
|
||||
show(prefix?: string, options?: IShowOptions): Promise<void> {
|
||||
if (this.useNewExperimentalVersion) {
|
||||
this.quickInputService.quickAccess.show(prefix, options);
|
||||
this.quickInputService.quickAccess.show(prefix, {
|
||||
quickNavigateConfiguration: options?.quickNavigateConfiguration,
|
||||
itemActivation: (() => {
|
||||
if (options?.autoFocus?.autoFocusSecondEntry) {
|
||||
return ItemActivation.SECOND;
|
||||
}
|
||||
|
||||
if (options?.autoFocus?.autoFocusLastEntry) {
|
||||
return ItemActivation.LAST;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
})()
|
||||
});
|
||||
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
@@ -21,8 +21,8 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { KeyMod, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { IThemeService, IColorTheme, ICssStyleCollector, registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { contrastBorder, editorBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { contrastBorder } from 'vs/platform/theme/common/colorRegistry';
|
||||
import { SIDE_BAR_TITLE_FOREGROUND, SIDE_BAR_BACKGROUND, SIDE_BAR_FOREGROUND, SIDE_BAR_BORDER, SIDE_BAR_DRAG_AND_DROP_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||
import { EventType, addDisposableListener, trackFocus } from 'vs/base/browser/dom';
|
||||
@@ -333,23 +333,6 @@ class FocusSideBarAction extends Action {
|
||||
}
|
||||
}
|
||||
|
||||
registerThemingParticipant((theme: IColorTheme, collector: ICssStyleCollector) => {
|
||||
|
||||
// Sidebar Background: since views can host editors, we apply a background rule if the sidebar background
|
||||
// color is different from the editor background color. This is a bit of a hack though. The better way
|
||||
// would be to have a way to push the background color onto each editor widget itself somehow.
|
||||
const sidebarBackground = theme.getColor(SIDE_BAR_BACKGROUND);
|
||||
if (sidebarBackground && sidebarBackground !== theme.getColor(editorBackground)) {
|
||||
collector.addRule(`
|
||||
.monaco-workbench .part.sidebar > .content .monaco-editor,
|
||||
.monaco-workbench .part.sidebar > .content .monaco-editor .margin,
|
||||
.monaco-workbench .part.sidebar > .content .monaco-editor .monaco-editor-background {
|
||||
background-color: ${sidebarBackground};
|
||||
}
|
||||
`);
|
||||
}
|
||||
});
|
||||
|
||||
const registry = Registry.as<IWorkbenchActionRegistry>(ActionExtensions.WorkbenchActions);
|
||||
registry.registerWorkbenchAction(SyncActionDescriptor.create(FocusSideBarAction, FocusSideBarAction.ID, FocusSideBarAction.LABEL, {
|
||||
primary: KeyMod.CtrlCmd | KeyCode.KEY_0
|
||||
|
||||
@@ -35,6 +35,7 @@ import { values } from 'vs/base/common/map';
|
||||
import { assertIsDefined } from 'vs/base/common/types';
|
||||
import { Emitter, Event } from 'vs/base/common/event';
|
||||
import { Command } from 'vs/editor/common/modes';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
interface IPendingStatusbarEntry {
|
||||
id: string;
|
||||
@@ -55,7 +56,7 @@ interface IStatusbarViewModelEntry {
|
||||
|
||||
class StatusbarViewModel extends Disposable {
|
||||
|
||||
private static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
|
||||
static readonly HIDDEN_ENTRIES_KEY = 'workbench.statusbar.hidden';
|
||||
|
||||
private readonly _entries: IStatusbarViewModelEntry[] = [];
|
||||
get entries(): IStatusbarViewModelEntry[] { return this._entries; }
|
||||
@@ -354,12 +355,14 @@ export class StatusbarPart extends Part implements IStatusbarService {
|
||||
@IWorkspaceContextService private readonly contextService: IWorkspaceContextService,
|
||||
@IStorageService storageService: IStorageService,
|
||||
@IWorkbenchLayoutService layoutService: IWorkbenchLayoutService,
|
||||
@IContextMenuService private contextMenuService: IContextMenuService
|
||||
@IContextMenuService private contextMenuService: IContextMenuService,
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService,
|
||||
) {
|
||||
super(Parts.STATUSBAR_PART, { hasTitle: false }, themeService, storageService, layoutService);
|
||||
|
||||
this.viewModel = this._register(new StatusbarViewModel(storageService));
|
||||
this.onDidChangeEntryVisibility = this.viewModel.onDidChangeEntryVisibility;
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: StatusbarViewModel.HIDDEN_ENTRIES_KEY, version: 1 });
|
||||
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
@@ -467,7 +467,7 @@ export class TitlebarPart extends Part implements ITitleService {
|
||||
if ((!isWeb && isMacintosh) || this.currentMenubarVisibility === 'hidden') {
|
||||
this.title.style.zoom = `${1 / getZoomFactor()}`;
|
||||
} else {
|
||||
this.title.style.zoom = null;
|
||||
this.title.style.zoom = '';
|
||||
}
|
||||
|
||||
runAtThisOrScheduleAtNextAnimationFrame(() => this.adjustTitleMarginToCenter());
|
||||
|
||||
@@ -414,7 +414,7 @@ export class TreeView extends Disposable implements ITreeView {
|
||||
const actionViewItemProvider = (action: IAction) => action instanceof MenuItemAction ? this.instantiationService.createInstance(ContextAwareMenuEntryActionViewItem, action) : undefined;
|
||||
const treeMenus = this._register(this.instantiationService.createInstance(TreeMenus, this.id));
|
||||
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.viewContainer.id }, () => task));
|
||||
const dataSource = this.instantiationService.createInstance(TreeDataSource, this, <T>(task: Promise<T>) => this.progressService.withProgress({ location: this.id }, () => task));
|
||||
const aligner = new Aligner(this.themeService);
|
||||
const renderer = this.instantiationService.createInstance(TreeRenderer, this.id, treeMenus, this.treeLabels, actionViewItemProvider, aligner);
|
||||
|
||||
@@ -1015,7 +1015,7 @@ export class CustomTreeView extends TreeView {
|
||||
|
||||
private activate() {
|
||||
if (!this.activated) {
|
||||
this.progressService.withProgress({ location: this.viewContainer.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
||||
this.progressService.withProgress({ location: this.id }, () => this.extensionService.activateByEvent(`onView:${this.id}`))
|
||||
.then(() => timeout(2000))
|
||||
.then(() => {
|
||||
this.updateMessage();
|
||||
|
||||
@@ -22,7 +22,6 @@ import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IPaneComposite } from 'vs/workbench/common/panecomposite';
|
||||
import { IPanelService } from 'vs/workbench/services/panel/common/panelService';
|
||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { VIEW_ID as SEARCH_VIEW_ID } from 'vs/workbench/services/search/common/search';
|
||||
import { ViewPaneContainer } from 'vs/workbench/browser/parts/views/viewPaneContainer';
|
||||
import { PaneCompositePanel, PanelRegistry, PanelDescriptor, Extensions as PanelExtensions } from 'vs/workbench/browser/panel';
|
||||
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
|
||||
@@ -571,33 +570,28 @@ export class ViewsService extends Disposable implements IViewsService {
|
||||
}
|
||||
}));
|
||||
|
||||
const newLocation = location === ViewContainerLocation.Panel ? ViewContainerLocation.Sidebar : ViewContainerLocation.Panel;
|
||||
disposables.add(registerAction2(class MoveViewAction extends Action2 {
|
||||
disposables.add(registerAction2(class ResetViewLocationAction extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: `${viewDescriptor.id}.moveView`,
|
||||
id: `${viewDescriptor.id}.resetViewLocation`,
|
||||
title: {
|
||||
original: newLocation === ViewContainerLocation.Sidebar ? 'Move to Sidebar' : 'Move to Panel',
|
||||
value: newLocation === ViewContainerLocation.Sidebar ? localize('moveViewToSidebar', "Move to Sidebar") : localize('moveViewToPanel', "Move to Panel")
|
||||
original: 'Reset View Location',
|
||||
value: localize('resetViewLocation', "Reset View Location")
|
||||
},
|
||||
menu: [{
|
||||
id: MenuId.ViewTitleContext,
|
||||
when: ContextKeyExpr.or(
|
||||
ContextKeyExpr.and(
|
||||
ContextKeyExpr.equals('view', viewDescriptor.id),
|
||||
ContextKeyExpr.has(`${viewDescriptor.id}.canMove`),
|
||||
ContextKeyExpr.equals('config.workbench.view.experimental.allowMovingToNewContainer', true)),
|
||||
ContextKeyExpr.and(
|
||||
ContextKeyExpr.equals('view', viewDescriptor.id),
|
||||
ContextKeyExpr.has(`${viewDescriptor.id}.canMove`),
|
||||
ContextKeyExpr.equals('view', SEARCH_VIEW_ID)
|
||||
ContextKeyExpr.equals(`${viewDescriptor.id}.defaultViewLocation`, false)
|
||||
)
|
||||
)
|
||||
}],
|
||||
});
|
||||
}
|
||||
run(accessor: ServicesAccessor): void {
|
||||
accessor.get(IViewDescriptorService).moveViewToLocation(viewDescriptor, newLocation);
|
||||
const viewDescriptorService = accessor.get(IViewDescriptorService);
|
||||
viewDescriptorService.moveViewsToContainer([viewDescriptor], viewDescriptorService.getDefaultContainer(viewDescriptor.id)!);
|
||||
accessor.get(IViewsService).openView(viewDescriptor.id, true);
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -226,11 +226,6 @@ import { workbenchConfigurationNodeBase } from 'vs/workbench/common/configuratio
|
||||
'default': true, // {{SQL CARBON EDIT}} - change the default value from false to true.
|
||||
'description': nls.localize('viewVisibility', "Controls the visibility of view header actions. View header actions may either be always visible, or only visible when that view is focused or hovered over.")
|
||||
},
|
||||
'workbench.view.experimental.allowMovingToNewContainer': {
|
||||
'type': 'boolean',
|
||||
'default': true,
|
||||
'description': nls.localize('movingViewContainer', "Controls whether specific views will have a context menu entry allowing them to be moved to a new container. Currently, this setting only affects the outline view and views contributed by extensions.")
|
||||
},
|
||||
'workbench.fontAliasing': {
|
||||
'type': 'string',
|
||||
'enum': ['default', 'antialiased', 'none', 'auto'],
|
||||
|
||||
@@ -20,7 +20,7 @@ import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { WorkbenchAsyncDataTree, IListService } from 'vs/platform/list/browser/listService';
|
||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { PANEL_BACKGROUND } from 'vs/workbench/common/theme';
|
||||
import { IColorMapping } from 'vs/platform/theme/common/styler';
|
||||
|
||||
export const COMMENTS_VIEW_ID = 'workbench.panel.comments';
|
||||
export const COMMENTS_VIEW_TITLE = 'Comments';
|
||||
@@ -149,10 +149,15 @@ export class CommentNodeRenderer implements IListRenderer<ITreeNode<CommentNode>
|
||||
}
|
||||
}
|
||||
|
||||
export interface ICommentsListOptions {
|
||||
overrideStyles?: IColorMapping;
|
||||
}
|
||||
|
||||
export class CommentsList extends WorkbenchAsyncDataTree<any, any> {
|
||||
constructor(
|
||||
labels: ResourceLabels,
|
||||
container: HTMLElement,
|
||||
options: ICommentsListOptions,
|
||||
@IContextKeyService contextKeyService: IContextKeyService,
|
||||
@IListService listService: IListService,
|
||||
@IThemeService themeService: IThemeService,
|
||||
@@ -202,9 +207,7 @@ export class CommentsList extends WorkbenchAsyncDataTree<any, any> {
|
||||
collapseByDefault: () => {
|
||||
return false;
|
||||
},
|
||||
overrideStyles: {
|
||||
listBackground: PANEL_BACKGROUND
|
||||
}
|
||||
overrideStyles: options.overrideStyles
|
||||
},
|
||||
contextKeyService,
|
||||
listService,
|
||||
|
||||
@@ -149,7 +149,7 @@ export class CommentsPanel extends ViewPane {
|
||||
|
||||
private createTree(): void {
|
||||
this.treeLabels = this._register(this.instantiationService.createInstance(ResourceLabels, this));
|
||||
this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer));
|
||||
this.tree = this._register(this.instantiationService.createInstance(CommentsList, this.treeLabels, this.treeContainer, { overrideStyles: { listBackground: this.getBackgroundColor() } }));
|
||||
|
||||
const commentsNavigator = this._register(ResourceNavigator.createTreeResourceNavigator(this.tree, { openOnFocus: true }));
|
||||
this._register(commentsNavigator.onDidOpenResource(e => {
|
||||
|
||||
@@ -13,7 +13,7 @@ import { generateUuid } from 'vs/base/common/uuid';
|
||||
import * as nls from 'vs/nls';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { EditorActivation, IEditorOptions, ITextEditorOptions } from 'vs/platform/editor/common/editor';
|
||||
import { FileOperation, IFileService } from 'vs/platform/files/common/files';
|
||||
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/common/quickInput';
|
||||
@@ -21,16 +21,16 @@ import * as colorRegistry from 'vs/platform/theme/common/colorRegistry';
|
||||
import { registerThemingParticipant } from 'vs/platform/theme/common/themeService';
|
||||
import { EditorServiceImpl } from 'vs/workbench/browser/parts/editor/editor';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { EditorInput, EditorOptions, IEditorInput, IEditorPane, GroupIdentifier } from 'vs/workbench/common/editor';
|
||||
import { EditorInput, EditorOptions, GroupIdentifier, IEditorInput, IEditorPane } from 'vs/workbench/common/editor';
|
||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||
import { webviewEditorsExtensionPoint } from 'vs/workbench/contrib/customEditor/browser/extensionPoint';
|
||||
import { CONTEXT_CUSTOM_EDITORS, CONTEXT_FOCUSED_CUSTOM_EDITOR_IS_EDITABLE, CustomEditorInfo, CustomEditorInfoCollection, CustomEditorPriority, CustomEditorSelector, ICustomEditorService } from 'vs/workbench/contrib/customEditor/common/customEditor';
|
||||
import { CustomEditorModelManager } from 'vs/workbench/contrib/customEditor/common/customEditorModelManager';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
import { IWebviewService, webviewHasOwnEditFunctionsContext } from 'vs/workbench/contrib/webview/browser/webview';
|
||||
import { IEditorGroup, IEditorGroupsService } from 'vs/workbench/services/editor/common/editorGroupsService';
|
||||
import { IEditorService, IOpenEditorOverride } from 'vs/workbench/services/editor/common/editorService';
|
||||
import { CustomEditorInput } from './customEditorInput';
|
||||
import { FileEditorInput } from 'vs/workbench/contrib/files/common/editors/fileEditorInput';
|
||||
|
||||
export const defaultEditorId = 'default';
|
||||
|
||||
@@ -284,6 +284,10 @@ export class CustomEditorService extends Disposable implements ICustomEditorServ
|
||||
): Promise<IEditorPane | undefined> {
|
||||
const targetGroup = group || this.editorGroupService.activeGroup;
|
||||
|
||||
if (options && typeof options.activation === 'undefined') {
|
||||
options = { ...options, activation: options.preserveFocus ? EditorActivation.RESTORE : undefined };
|
||||
}
|
||||
|
||||
// Try to replace existing editors for resource
|
||||
const existingEditors = targetGroup.editors.filter(editor => editor.resource && isEqual(editor.resource, resource));
|
||||
if (existingEditors.length) {
|
||||
@@ -454,7 +458,11 @@ export class CustomEditorContribution extends Disposable implements IWorkbenchCo
|
||||
const existingEditorForResource = group.editors.find(editor => isEqual(resource, editor.resource));
|
||||
if (existingEditorForResource) {
|
||||
return {
|
||||
override: this.editorService.openEditor(existingEditorForResource, { ...options, ignoreOverrides: true }, group)
|
||||
override: this.editorService.openEditor(existingEditorForResource, {
|
||||
...options,
|
||||
ignoreOverrides: true,
|
||||
activation: options?.preserveFocus ? EditorActivation.RESTORE : undefined,
|
||||
}, group)
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -96,6 +96,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewE
|
||||
id: OpenDebugPanelAction.ID,
|
||||
keybindings: openPanelKb
|
||||
},
|
||||
order: 3,
|
||||
hideIfEmpty: true
|
||||
}, ViewContainerLocation.Panel);
|
||||
|
||||
|
||||
@@ -234,7 +234,7 @@ export class DebugSession implements IDebugSession {
|
||||
|
||||
this.initialized = true;
|
||||
this._onDidChangeState.fire();
|
||||
this.model.setExceptionBreakpoints(this.raw!.capabilities.exceptionBreakpointFilters || []);
|
||||
this.model.setExceptionBreakpoints((this.raw && this.raw.capabilities.exceptionBreakpointFilters) || []);
|
||||
} catch (err) {
|
||||
this.initialized = true;
|
||||
this._onDidChangeState.fire();
|
||||
|
||||
@@ -208,9 +208,20 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
||||
}));
|
||||
this._register(this.configurationService.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('debug.console.lineHeight') || e.affectsConfiguration('debug.console.fontSize') || e.affectsConfiguration('debug.console.fontFamily')) {
|
||||
this.onDidFontChange();
|
||||
this.onDidStyleChange();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.themeService.onDidColorThemeChange(e => {
|
||||
this.onDidStyleChange();
|
||||
}));
|
||||
|
||||
this._register(this.viewDescriptorService.onDidChangeLocation(e => {
|
||||
if (e.views.some(v => v.id === this.id)) {
|
||||
this.onDidStyleChange();
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.editorService.onDidActiveEditorChange(() => {
|
||||
this.setMode();
|
||||
}));
|
||||
@@ -253,14 +264,15 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
||||
}
|
||||
}
|
||||
|
||||
private onDidFontChange(): void {
|
||||
private onDidStyleChange(): void {
|
||||
if (this.styleElement) {
|
||||
const debugConsole = this.configurationService.getValue<IDebugConfiguration>('debug').console;
|
||||
const fontSize = debugConsole.fontSize;
|
||||
const fontFamily = debugConsole.fontFamily === 'default' ? 'var(--monaco-monospace-font)' : debugConsole.fontFamily;
|
||||
const lineHeight = debugConsole.lineHeight ? `${debugConsole.lineHeight}px` : '1.4em';
|
||||
const backgroundColor = this.themeService.getColorTheme().getColor(this.getBackgroundColor());
|
||||
|
||||
// Set the font size, font family, line height and align the twistie to be centered
|
||||
// Set the font size, font family, line height and align the twistie to be centered, and input theme color
|
||||
this.styleElement.innerHTML = `
|
||||
.repl .repl-tree .expression {
|
||||
font-size: ${fontSize}px;
|
||||
@@ -274,6 +286,10 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
||||
.repl .repl-tree .monaco-tl-twistie {
|
||||
background-position-y: calc(100% - ${fontSize * 1.4 / 2 - 8}px);
|
||||
}
|
||||
|
||||
.repl .repl-input-wrapper .monaco-editor .lines-content {
|
||||
background-color: ${backgroundColor};
|
||||
}
|
||||
`;
|
||||
|
||||
this.tree.rerender();
|
||||
@@ -510,7 +526,7 @@ export class Repl extends ViewPane implements IHistoryNavigationWidget {
|
||||
// Make sure to select the session if debugging is already active
|
||||
this.selectSession();
|
||||
this.styleElement = dom.createStyleSheet(this.container);
|
||||
this.onDidFontChange();
|
||||
this.onDidStyleChange();
|
||||
}
|
||||
|
||||
private createReplInput(container: HTMLElement): void {
|
||||
|
||||
@@ -80,8 +80,11 @@ export class VariablesView extends ViewPane {
|
||||
if (stackFrame) {
|
||||
const scopes = await stackFrame.getScopes();
|
||||
// Expand the first scope if it is not expensive and if there is no expansion state (all are collapsed)
|
||||
if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0 && !scopes[0].expensive) {
|
||||
this.tree.expand(scopes[0]);
|
||||
if (scopes.every(s => this.tree.getNode(s).collapsed) && scopes.length > 0) {
|
||||
const toExpand = scopes.filter(s => !s.expensive).shift();
|
||||
if (toExpand) {
|
||||
this.tree.expand(toExpand);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -305,9 +305,19 @@ export class StackFrame implements IStackFrame {
|
||||
getScopes(): Promise<IScope[]> {
|
||||
if (!this.scopes) {
|
||||
this.scopes = this.thread.session.scopes(this.frameId, this.thread.threadId).then(response => {
|
||||
return response && response.body && response.body.scopes ?
|
||||
response.body.scopes.map((rs, index) => new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,
|
||||
rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined)) : [];
|
||||
if (!response || !response.body || !response.body.scopes) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const scopeNameIndexes = new Map<string, number>();
|
||||
return response.body.scopes.map(rs => {
|
||||
const previousIndex = scopeNameIndexes.get(rs.name);
|
||||
const index = typeof previousIndex === 'number' ? previousIndex + 1 : 0;
|
||||
scopeNameIndexes.set(rs.name, index);
|
||||
return new Scope(this, index, rs.name, rs.variablesReference, rs.expensive, rs.namedVariables, rs.indexedVariables,
|
||||
rs.line && rs.column && rs.endLine && rs.endColumn ? new Range(rs.line, rs.column, rs.endLine, rs.endColumn) : undefined);
|
||||
|
||||
});
|
||||
}, err => [new ErrorScope(this, 0, err.message)]);
|
||||
}
|
||||
|
||||
|
||||
@@ -48,12 +48,10 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid
|
||||
}
|
||||
|
||||
// Extension name typed: offer to search it
|
||||
else {
|
||||
return [genericSearchPickItem];
|
||||
}
|
||||
return [genericSearchPickItem];
|
||||
}
|
||||
|
||||
protected async getPicksForExtensionId(filter: string, fallback: IPickerQuickAccessItem, token: CancellationToken): Promise<Array<IPickerQuickAccessItem | IQuickPickSeparator>> {
|
||||
private async getPicksForExtensionId(filter: string, fallback: IPickerQuickAccessItem, token: CancellationToken): Promise<Array<IPickerQuickAccessItem | IQuickPickSeparator>> {
|
||||
try {
|
||||
const galleryResult = await this.galleryService.query({ names: [filter], pageSize: 1 }, token);
|
||||
if (token.isCancellationRequested) {
|
||||
@@ -63,12 +61,12 @@ export class InstallExtensionQuickAccessProvider extends PickerQuickAccessProvid
|
||||
const galleryExtension = galleryResult.firstPage[0];
|
||||
if (!galleryExtension) {
|
||||
return [fallback];
|
||||
} else {
|
||||
return [{
|
||||
label: localize('install', "Press Enter to install extension '{0}'.", filter),
|
||||
accept: () => this.installExtension(galleryExtension, filter)
|
||||
}];
|
||||
}
|
||||
|
||||
return [{
|
||||
label: localize('install', "Press Enter to install extension '{0}'.", filter),
|
||||
accept: () => this.installExtension(galleryExtension, filter)
|
||||
}];
|
||||
} catch (error) {
|
||||
if (token.isCancellationRequested) {
|
||||
return []; // expected error
|
||||
|
||||
@@ -46,6 +46,7 @@ import { IFilesConfigurationService } from 'vs/workbench/services/filesConfigura
|
||||
import { IWorkingCopyService, IWorkingCopy } from 'vs/workbench/services/workingCopy/common/workingCopyService';
|
||||
import { sequence } from 'vs/base/common/async';
|
||||
import { IWorkingCopyFileService } from 'vs/workbench/services/workingCopy/common/workingCopyFileService';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
export const NEW_FILE_COMMAND_ID = 'explorer.newFile';
|
||||
export const NEW_FILE_LABEL = nls.localize('newFile', "New File");
|
||||
@@ -519,9 +520,10 @@ export class GlobalCompareResourcesAction extends Action {
|
||||
return undefined;
|
||||
});
|
||||
|
||||
once(this.quickOpenService.onHide)((() => toDispose.dispose()));
|
||||
|
||||
// Bring up quick open
|
||||
await this.quickOpenService.show('', { autoFocus: { autoFocusSecondEntry: true } });
|
||||
toDispose.dispose(); // make sure to unbind if quick open is closing
|
||||
} else {
|
||||
this.notificationService.info(nls.localize('openFileToCompare', "Open a file first to compare it with another file."));
|
||||
}
|
||||
|
||||
@@ -112,6 +112,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewC
|
||||
id: Constants.MARKERS_CONTAINER_ID,
|
||||
name: Messages.MARKERS_PANEL_TITLE_PROBLEMS,
|
||||
hideIfEmpty: true,
|
||||
order: 0,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [Constants.MARKERS_CONTAINER_ID, Constants.MARKERS_VIEW_STORAGE_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
|
||||
focusCommand: {
|
||||
id: ToggleMarkersPanelAction.ID, keybindings: {
|
||||
@@ -122,6 +123,7 @@ const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewC
|
||||
|
||||
Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry).registerViews([{
|
||||
id: Constants.MARKERS_VIEW_ID,
|
||||
containerIcon: 'codicon-warning',
|
||||
name: Messages.MARKERS_PANEL_TITLE_PROBLEMS,
|
||||
canToggleVisibility: false,
|
||||
canMoveView: true,
|
||||
|
||||
@@ -201,6 +201,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
mouseSupport: true,
|
||||
multipleSelectionSupport: false,
|
||||
enableKeyboardNavigation: true,
|
||||
additionalScrollHeight: 0,
|
||||
overrideStyles: {
|
||||
listBackground: editorBackground,
|
||||
listActiveSelectionBackground: editorBackground,
|
||||
@@ -419,6 +420,7 @@ export class NotebookEditor extends BaseEditor implements INotebookEditor {
|
||||
DOM.toggleClass(this.rootElement, 'mid-width', dimension.width < 1000 && dimension.width >= 600);
|
||||
DOM.toggleClass(this.rootElement, 'narrow-width', dimension.width < 600);
|
||||
DOM.size(this.body, dimension.width, dimension.height);
|
||||
this.list?.updateOptions({ additionalScrollHeight: dimension.height });
|
||||
this.list?.layout(dimension.height, dimension.width);
|
||||
this.eventDispatcher?.emit([new NotebookLayoutChangedEvent({ width: true, fontInfo: true }, this.getLayoutInfo())]);
|
||||
}
|
||||
@@ -742,6 +744,13 @@ export const focusedCellIndicator = registerColor('notebook.focusedCellIndicator
|
||||
hc: new Color(new RGBA(0, 73, 122))
|
||||
}, nls.localize('notebook.focusedCellIndicator', "The color of the focused notebook cell indicator."));
|
||||
|
||||
export const notebookOutputContainerColor = registerColor('notebook.outputContainerBackgroundColor', {
|
||||
dark: new Color(new RGBA(255, 255, 255, 0.06)),
|
||||
light: new Color(new RGBA(228, 230, 241)),
|
||||
hc: null
|
||||
}
|
||||
, nls.localize('notebook.outputContainerBackgroundColor', "The Color of the notebook output container background."));
|
||||
|
||||
|
||||
registerThemingParticipant((theme, collector) => {
|
||||
const color = getExtraColor(theme, embeddedEditorBackground, { dark: 'rgba(0, 0, 0, .4)', extra_dark: 'rgba(200, 235, 255, .064)', light: '#f4f4f4', hc: null });
|
||||
@@ -776,10 +785,10 @@ registerThemingParticipant((theme, collector) => {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor blockquote { border-color: ${quoteBorder}; }`);
|
||||
}
|
||||
|
||||
const inactiveListItem = theme.getColor('list.inactiveSelectionBackground');
|
||||
const containerBackground = theme.getColor(notebookOutputContainerColor);
|
||||
|
||||
if (inactiveListItem) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${inactiveListItem}; }`);
|
||||
if (containerBackground) {
|
||||
collector.addRule(`.monaco-workbench .part.editor > .content .notebook-editor .output { background-color: ${containerBackground}; }`);
|
||||
}
|
||||
|
||||
const focusedCellIndicatorColor = theme.getColor(focusedCellIndicator);
|
||||
|
||||
@@ -94,9 +94,13 @@ abstract class AbstractCellRenderer {
|
||||
horizontalHasArrows: false,
|
||||
alwaysConsumeMouseWheel: false
|
||||
},
|
||||
overviewRulerLanes: 3,
|
||||
renderLineHighlightOnlyWhenFocus: true,
|
||||
overviewRulerLanes: 0,
|
||||
selectOnLineNumbers: false,
|
||||
lineNumbers: 'off',
|
||||
lineDecorationsWidth: 0,
|
||||
glyphMargin: false,
|
||||
fixedOverflowWidgets: false,
|
||||
lineNumbersMinChars: 1,
|
||||
minimap: { enabled: false },
|
||||
};
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ import { IQuickInputService, IQuickPickItem } from 'vs/platform/quickinput/commo
|
||||
import { INotebookService } from 'vs/workbench/contrib/notebook/browser/notebookService';
|
||||
import { CodeCellViewModel } from 'vs/workbench/contrib/notebook/browser/viewModel/codeCellViewModel';
|
||||
import { EDITOR_TOP_PADDING, EDITOR_BOTTOM_PADDING } from 'vs/workbench/contrib/notebook/browser/constants';
|
||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||
|
||||
interface IMimeTypeRenderer extends IQuickPickItem {
|
||||
index: number;
|
||||
@@ -28,7 +29,8 @@ export class CodeCell extends Disposable {
|
||||
private viewCell: CodeCellViewModel,
|
||||
private templateData: CellRenderTemplate,
|
||||
@INotebookService private notebookService: INotebookService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@IModeService private readonly _modeService: IModeService
|
||||
) {
|
||||
super();
|
||||
|
||||
@@ -85,6 +87,11 @@ export class CodeCell extends Disposable {
|
||||
templateData.editor?.updateOptions({ readOnly: !(viewCell.getEvaluatedMetadata(notebookEditor.viewModel?.metadata).editable) });
|
||||
}));
|
||||
|
||||
this._register(viewCell.onDidChangeLanguage((e) => {
|
||||
const mode = this._modeService.create(e);
|
||||
templateData.editor?.getModel()?.setMode(mode.languageIdentifier);
|
||||
}));
|
||||
|
||||
let cellWidthResizeObserver = getResizesObserver(templateData.editorContainer!, {
|
||||
width: width,
|
||||
height: totalHeight
|
||||
|
||||
@@ -34,6 +34,8 @@ export abstract class BaseCellViewModel extends Disposable {
|
||||
public readonly onDidChangeCursorSelection: Event<void> = this._onDidChangeCursorSelection.event;
|
||||
protected readonly _onDidChangeMetadata: Emitter<NotebookCellMetadata | undefined> = this._register(new Emitter<NotebookCellMetadata | undefined>());
|
||||
public readonly onDidChangeMetadata: Event<NotebookCellMetadata | undefined> = this._onDidChangeMetadata.event;
|
||||
protected readonly _onDidChangeLanguage: Emitter<string> = this._register(new Emitter<string>());
|
||||
public readonly onDidChangeLanguage: Event<string> = this._onDidChangeLanguage.event;
|
||||
get handle() {
|
||||
return this.cell.handle;
|
||||
}
|
||||
@@ -100,6 +102,10 @@ export abstract class BaseCellViewModel extends Disposable {
|
||||
constructor(readonly viewType: string, readonly notebookHandle: number, readonly cell: ICell, public id: string) {
|
||||
super();
|
||||
|
||||
this._register(cell.onDidChangeLanguage((e) => {
|
||||
this._onDidChangeLanguage.fire(e);
|
||||
}));
|
||||
|
||||
this._register(cell.onDidChangeMetadata((e) => {
|
||||
this._onDidChangeMetadata.fire(e);
|
||||
}));
|
||||
|
||||
@@ -89,6 +89,12 @@ export class CodeCellViewModel extends BaseCellViewModel implements ICellViewMod
|
||||
this.layoutChange({ outerWidth: e.value.width, font: e.value.fontInfo });
|
||||
}
|
||||
}));
|
||||
|
||||
this._register(this.onDidChangeLanguage((e) => {
|
||||
if (this._textModel && !this._textModel.isDisposed()) {
|
||||
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
layoutChange(state: CodeCellLayoutChangeEvent) {
|
||||
|
||||
@@ -18,6 +18,9 @@ export class NotebookCellTextModel implements ICell {
|
||||
private _onDidChangeMetadata = new Emitter<NotebookCellMetadata | undefined>();
|
||||
onDidChangeMetadata: Event<NotebookCellMetadata | undefined> = this._onDidChangeMetadata.event;
|
||||
|
||||
private _onDidChangeLanguage = new Emitter<string>();
|
||||
onDidChangeLanguage: Event<string> = this._onDidChangeLanguage.event;
|
||||
|
||||
private _outputs: IOutput[];
|
||||
|
||||
get outputs(): IOutput[] {
|
||||
@@ -44,13 +47,22 @@ export class NotebookCellTextModel implements ICell {
|
||||
this._onDidChangeMetadata.fire(this._metadata);
|
||||
}
|
||||
|
||||
get language() {
|
||||
return this._language;
|
||||
}
|
||||
|
||||
set language(newLanguage: string) {
|
||||
this._language = newLanguage;
|
||||
this._onDidChangeLanguage.fire(newLanguage);
|
||||
}
|
||||
|
||||
private _buffer: PieceTreeTextBufferFactory | null = null;
|
||||
|
||||
constructor(
|
||||
readonly uri: URI,
|
||||
public handle: number,
|
||||
private _source: string[],
|
||||
public language: string,
|
||||
private _language: string,
|
||||
public cellKind: CellKind,
|
||||
outputs: IOutput[],
|
||||
metadata: NotebookCellMetadata | undefined
|
||||
|
||||
@@ -24,6 +24,7 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
languages: string[] = [];
|
||||
metadata: NotebookDocumentMetadata | undefined = { editable: true };
|
||||
renderers = new Set<number>();
|
||||
private _isUntitled: boolean | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
public handle: number,
|
||||
@@ -36,6 +37,11 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
|
||||
updateLanguages(languages: string[]) {
|
||||
this.languages = languages;
|
||||
|
||||
// TODO@rebornix metadata: default language for cell
|
||||
if (this._isUntitled && languages.length && this.cells.length) {
|
||||
this.cells[0].language = languages[0];
|
||||
}
|
||||
}
|
||||
|
||||
updateNotebookMetadata(metadata: NotebookDocumentMetadata) {
|
||||
@@ -57,7 +63,27 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
});
|
||||
}
|
||||
|
||||
insertTemplateCell(cell: NotebookCellTextModel) {
|
||||
if (this.cells.length > 0 || this._isUntitled !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isUntitled = true;
|
||||
this.cells = [cell];
|
||||
|
||||
let dirtyStateListener = cell.onDidChangeContent(() => {
|
||||
this._isUntitled = false;
|
||||
this._onDidChangeContent.fire();
|
||||
});
|
||||
|
||||
this._cellListeners.set(cell.handle, dirtyStateListener);
|
||||
this._onDidChangeContent.fire();
|
||||
return;
|
||||
}
|
||||
|
||||
insertNewCell(index: number, cell: NotebookCellTextModel): void {
|
||||
this._isUntitled = false;
|
||||
|
||||
this._mapping.set(cell.handle, cell);
|
||||
this.cells.splice(index, 0, cell);
|
||||
let dirtyStateListener = cell.onDidChangeContent(() => {
|
||||
@@ -70,6 +96,8 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
}
|
||||
|
||||
removeCell(index: number) {
|
||||
this._isUntitled = false;
|
||||
|
||||
let cell = this.cells[index];
|
||||
this._cellListeners.get(cell.handle)?.dispose();
|
||||
this._cellListeners.delete(cell.handle);
|
||||
@@ -80,6 +108,12 @@ export class NotebookTextModel extends Disposable implements INotebookTextModel
|
||||
|
||||
// TODO@rebornix should this trigger content change event?
|
||||
$spliceNotebookCells(splices: NotebookCellsSplice[]): void {
|
||||
if (!splices.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._isUntitled = false;
|
||||
|
||||
splices.reverse().forEach(splice => {
|
||||
let cellDtos = splice[2];
|
||||
let newCells = cellDtos.map(cell => {
|
||||
|
||||
@@ -136,6 +136,7 @@ export interface ICell {
|
||||
metadata?: NotebookCellMetadata;
|
||||
onDidChangeOutputs?: Event<NotebookCellOutputsSplice[]>;
|
||||
onDidChangeMetadata: Event<NotebookCellMetadata | undefined>;
|
||||
onDidChangeLanguage: Event<string>;
|
||||
resolveTextBufferFactory(): PieceTreeTextBufferFactory;
|
||||
// TODO@rebornix it should be later on replaced by moving textmodel resolution into CellTextModel
|
||||
contentChange(): void;
|
||||
|
||||
@@ -27,6 +27,9 @@ export class TestCell implements ICell {
|
||||
onDidChangeOutputs: Event<NotebookCellOutputsSplice[]> = this._onDidChangeOutputs.event;
|
||||
private _onDidChangeMetadata = new Emitter<NotebookCellMetadata>();
|
||||
onDidChangeMetadata: Event<NotebookCellMetadata> = this._onDidChangeMetadata.event;
|
||||
private _onDidChangeLanguage = new Emitter<string>();
|
||||
onDidChangeLanguage: Event<string> = this._onDidChangeLanguage.event;
|
||||
|
||||
private _isDirty: boolean = false;
|
||||
private _outputs: IOutput[];
|
||||
|
||||
|
||||
@@ -61,6 +61,7 @@ const toggleOutputActionKeybindings = {
|
||||
const VIEW_CONTAINER: ViewContainer = Registry.as<IViewContainersRegistry>(ViewContainerExtensions.ViewContainersRegistry).registerViewContainer({
|
||||
id: OUTPUT_VIEW_ID,
|
||||
name: nls.localize('output', "Output"),
|
||||
order: 1,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [OUTPUT_VIEW_ID, OUTPUT_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
|
||||
focusCommand: { id: toggleOutputAcitonId, keybindings: toggleOutputActionKeybindings }
|
||||
}, ViewContainerLocation.Panel);
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import 'vs/css!./remoteViewlet';
|
||||
import 'vs/css!./media/remoteViewlet';
|
||||
import * as nls from 'vs/nls';
|
||||
import * as dom from 'vs/base/browser/dom';
|
||||
import { URI } from 'vs/base/common/uri';
|
||||
@@ -55,6 +55,7 @@ import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||
import { Event } from 'vs/base/common/event';
|
||||
import { ExtensionsRegistry, IExtensionPointUser } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { RemoteWindowActiveIndicator } from 'vs/workbench/contrib/remote/browser/remoteIndicator';
|
||||
|
||||
export interface HelpInformation {
|
||||
extensionDescription: IExtensionDescription;
|
||||
@@ -803,3 +804,5 @@ class RemoteAgentConnectionStatusListener implements IWorkbenchContribution {
|
||||
|
||||
const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegistry>(WorkbenchExtensions.Workbench);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentConnectionStatusListener, LifecyclePhase.Eventually);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting);
|
||||
|
||||
|
||||
@@ -0,0 +1,229 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||
import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteAgentConnection';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { RemoteConnectionState, Deprecated_RemoteAuthorityContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { isWeb } from 'vs/base/common/platform';
|
||||
import { once } from 'vs/base/common/functional';
|
||||
|
||||
const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu';
|
||||
const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close';
|
||||
const SHOW_CLOSE_REMOTE_COMMAND_ID = !isWeb; // web does not have a "Close Remote" command
|
||||
|
||||
export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private windowIndicatorEntry: IStatusbarEntryAccessor | undefined;
|
||||
private windowCommandMenu: IMenu;
|
||||
private hasWindowActions: boolean = false;
|
||||
private remoteAuthority: string | undefined;
|
||||
private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@IStatusbarService private readonly statusbarService: IStatusbarService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IHostService hostService: IHostService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService);
|
||||
this._register(this.windowCommandMenu);
|
||||
|
||||
const category = nls.localize('remote.category', "Remote");
|
||||
const that = this;
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: WINDOW_ACTIONS_COMMAND_ID,
|
||||
category,
|
||||
title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' },
|
||||
f1: true,
|
||||
});
|
||||
}
|
||||
run = () => that.showIndicatorActions(that.windowCommandMenu);
|
||||
});
|
||||
|
||||
this.remoteAuthority = environmentService.configuration.remoteAuthority;
|
||||
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(this.remoteAuthority || '');
|
||||
|
||||
if (this.remoteAuthority) {
|
||||
|
||||
if (SHOW_CLOSE_REMOTE_COMMAND_ID) {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: CLOSE_REMOTE_COMMAND_ID,
|
||||
category,
|
||||
title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' },
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
run = () => that.remoteAuthority && hostService.openWindow({ forceReuseWindow: true });
|
||||
});
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
|
||||
group: '6_close',
|
||||
command: {
|
||||
id: CLOSE_REMOTE_COMMAND_ID,
|
||||
title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection")
|
||||
},
|
||||
order: 3.5
|
||||
});
|
||||
}
|
||||
|
||||
// Pending entry until extensions are ready
|
||||
this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID);
|
||||
this.connectionState = 'initializing';
|
||||
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
this._register(connection.onDidStateChange((e) => {
|
||||
switch (e.type) {
|
||||
case PersistentConnectionEventType.ConnectionLost:
|
||||
case PersistentConnectionEventType.ReconnectionPermanentFailure:
|
||||
case PersistentConnectionEventType.ReconnectionRunning:
|
||||
case PersistentConnectionEventType.ReconnectionWait:
|
||||
this.setDisconnected(true);
|
||||
break;
|
||||
case PersistentConnectionEventType.ConnectionGain:
|
||||
this.setDisconnected(false);
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
extensionService.whenInstalledExtensionsRegistered().then(_ => {
|
||||
if (this.remoteAuthority) {
|
||||
this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator()));
|
||||
remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true));
|
||||
}
|
||||
this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions()));
|
||||
this.updateWindowIndicator();
|
||||
});
|
||||
}
|
||||
|
||||
private setDisconnected(isDisconnected: boolean): void {
|
||||
const newState = isDisconnected ? 'disconnected' : 'connected';
|
||||
if (this.connectionState !== newState) {
|
||||
this.connectionState = newState;
|
||||
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
|
||||
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? `disconnected/${this.remoteAuthority!}` : this.remoteAuthority!);
|
||||
this.updateWindowIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private updateWindowIndicator(): void {
|
||||
const windowActionCommand = (this.remoteAuthority || this.windowCommandMenu.getActions().length) ? WINDOW_ACTIONS_COMMAND_ID : undefined;
|
||||
if (this.remoteAuthority) {
|
||||
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
|
||||
if (this.connectionState !== 'disconnected') {
|
||||
this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand);
|
||||
} else {
|
||||
this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel), windowActionCommand);
|
||||
}
|
||||
} else {
|
||||
if (windowActionCommand) {
|
||||
this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand);
|
||||
} else if (this.windowIndicatorEntry) {
|
||||
this.windowIndicatorEntry.dispose();
|
||||
this.windowIndicatorEntry = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateWindowActions() {
|
||||
const newHasWindowActions = this.windowCommandMenu.getActions().length > 0;
|
||||
if (newHasWindowActions !== this.hasWindowActions) {
|
||||
this.hasWindowActions = newHasWindowActions;
|
||||
this.updateWindowIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
|
||||
const properties: IStatusbarEntry = {
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command
|
||||
};
|
||||
if (this.windowIndicatorEntry) {
|
||||
this.windowIndicatorEntry.update(properties);
|
||||
} else {
|
||||
this.windowIndicatorEntry = this.statusbarService.addEntry(properties, 'status.host', nls.localize('status.host', "Remote Host"), StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
|
||||
}
|
||||
}
|
||||
|
||||
private showIndicatorActions(menu: IMenu) {
|
||||
|
||||
const actions = menu.getActions();
|
||||
|
||||
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
|
||||
for (let actionGroup of actions) {
|
||||
if (items.length) {
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
for (let action of actionGroup[1]) {
|
||||
if (action instanceof MenuItemAction) {
|
||||
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
|
||||
if (action.item.category) {
|
||||
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
|
||||
label = nls.localize('cat.title', "{0}: {1}", category, label);
|
||||
}
|
||||
items.push({
|
||||
type: 'item',
|
||||
id: action.item.id,
|
||||
label
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SHOW_CLOSE_REMOTE_COMMAND_ID && this.remoteAuthority) {
|
||||
if (items.length) {
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
items.push({
|
||||
type: 'item',
|
||||
id: CLOSE_REMOTE_COMMAND_ID,
|
||||
label: nls.localize('closeRemote.title', 'Close Remote Connection')
|
||||
});
|
||||
}
|
||||
|
||||
const quickPick = this.quickInputService.createQuickPick();
|
||||
quickPick.items = items;
|
||||
quickPick.canSelectMany = false;
|
||||
once(quickPick.onDidAccept)((_ => {
|
||||
const selectedItems = quickPick.selectedItems;
|
||||
if (selectedItems.length === 1) {
|
||||
this.commandService.executeCommand(selectedItems[0].id!);
|
||||
}
|
||||
quickPick.hide();
|
||||
}));
|
||||
quickPick.show();
|
||||
}
|
||||
}
|
||||
@@ -5,23 +5,17 @@
|
||||
|
||||
import * as nls from 'vs/nls';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { STATUS_BAR_HOST_NAME_BACKGROUND, STATUS_BAR_HOST_NAME_FOREGROUND } from 'vs/workbench/common/theme';
|
||||
import { themeColorFromId } from 'vs/platform/theme/common/themeService';
|
||||
import { IRemoteAgentService } from 'vs/workbench/services/remote/common/remoteAgentService';
|
||||
import { Disposable } from 'vs/base/common/lifecycle';
|
||||
import { isMacintosh } from 'vs/base/common/platform';
|
||||
import { KeyMod, KeyChord, KeyCode } from 'vs/base/common/keyCodes';
|
||||
import { KeybindingsRegistry, KeybindingWeight } from 'vs/platform/keybinding/common/keybindingsRegistry';
|
||||
import { MenuId, IMenuService, MenuItemAction, IMenu, MenuRegistry, registerAction2, Action2 } from 'vs/platform/actions/common/actions';
|
||||
import { IWorkbenchContribution, IWorkbenchContributionsRegistry, Extensions as WorkbenchContributionsExtensions } from 'vs/workbench/common/contributions';
|
||||
import { LifecyclePhase } from 'vs/platform/lifecycle/common/lifecycle';
|
||||
import { StatusbarAlignment, IStatusbarService, IStatusbarEntryAccessor, IStatusbarEntry } from 'vs/workbench/services/statusbar/common/statusbar';
|
||||
import { ILabelService } from 'vs/platform/label/common/label';
|
||||
import { IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { ICommandService } from 'vs/platform/commands/common/commands';
|
||||
import { REMOTE_HOST_SCHEME } from 'vs/platform/remote/common/remoteHosts';
|
||||
import { IExtensionService } from 'vs/workbench/services/extensions/common/extensions';
|
||||
import { IQuickInputService, IQuickPickItem, IQuickPickSeparator } from 'vs/platform/quickinput/common/quickInput';
|
||||
import { ILogService } from 'vs/platform/log/common/log';
|
||||
import { IDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||
import { DialogChannel } from 'vs/platform/dialogs/electron-browser/dialogIpc';
|
||||
@@ -34,211 +28,11 @@ import { PersistentConnectionEventType } from 'vs/platform/remote/common/remoteA
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { IConfigurationRegistry, Extensions as ConfigurationExtensions } from 'vs/platform/configuration/common/configurationRegistry';
|
||||
import { IRemoteAuthorityResolverService } from 'vs/platform/remote/common/remoteAuthorityResolver';
|
||||
import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||
import { RemoteConnectionState, Deprecated_RemoteAuthorityContext, RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { RemoteFileDialogContext } from 'vs/workbench/browser/contextkeys';
|
||||
import { IDownloadService } from 'vs/platform/download/common/download';
|
||||
import { OpenLocalFileFolderCommand, OpenLocalFileCommand, OpenLocalFolderCommand, SaveLocalFileCommand } from 'vs/workbench/services/dialogs/browser/simpleFileDialog';
|
||||
import { INativeWorkbenchEnvironmentService } from 'vs/workbench/services/environment/electron-browser/environmentService';
|
||||
|
||||
const WINDOW_ACTIONS_COMMAND_ID = 'workbench.action.remote.showMenu';
|
||||
const CLOSE_REMOTE_COMMAND_ID = 'workbench.action.remote.close';
|
||||
|
||||
export class RemoteWindowActiveIndicator extends Disposable implements IWorkbenchContribution {
|
||||
|
||||
private windowIndicatorEntry: IStatusbarEntryAccessor | undefined;
|
||||
private windowCommandMenu: IMenu;
|
||||
private hasWindowActions: boolean = false;
|
||||
private remoteAuthority: string | undefined;
|
||||
private connectionState: 'initializing' | 'connected' | 'disconnected' | undefined = undefined;
|
||||
|
||||
constructor(
|
||||
@IStatusbarService private readonly statusbarService: IStatusbarService,
|
||||
@IWorkbenchEnvironmentService environmentService: IWorkbenchEnvironmentService,
|
||||
@ILabelService private readonly labelService: ILabelService,
|
||||
@IContextKeyService private contextKeyService: IContextKeyService,
|
||||
@IMenuService private menuService: IMenuService,
|
||||
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||
@ICommandService private readonly commandService: ICommandService,
|
||||
@IExtensionService extensionService: IExtensionService,
|
||||
@IRemoteAgentService remoteAgentService: IRemoteAgentService,
|
||||
@IRemoteAuthorityResolverService remoteAuthorityResolverService: IRemoteAuthorityResolverService,
|
||||
@IHostService hostService: IHostService
|
||||
) {
|
||||
super();
|
||||
|
||||
this.windowCommandMenu = this.menuService.createMenu(MenuId.StatusBarWindowIndicatorMenu, this.contextKeyService);
|
||||
this._register(this.windowCommandMenu);
|
||||
|
||||
const category = nls.localize('remote.category', "Remote");
|
||||
const that = this;
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: WINDOW_ACTIONS_COMMAND_ID,
|
||||
category,
|
||||
title: { value: nls.localize('remote.showMenu', "Show Remote Menu"), original: 'Show Remote Menu' },
|
||||
f1: true,
|
||||
});
|
||||
}
|
||||
run = () => that.showIndicatorActions(that.windowCommandMenu);
|
||||
});
|
||||
|
||||
this.remoteAuthority = environmentService.configuration.remoteAuthority;
|
||||
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(this.remoteAuthority || '');
|
||||
|
||||
if (this.remoteAuthority) {
|
||||
registerAction2(class extends Action2 {
|
||||
constructor() {
|
||||
super({
|
||||
id: CLOSE_REMOTE_COMMAND_ID,
|
||||
category,
|
||||
title: { value: nls.localize('remote.close', "Close Remote Connection"), original: 'Close Remote Connection' },
|
||||
f1: true
|
||||
});
|
||||
}
|
||||
run = () => that.remoteAuthority && hostService.openWindow({ forceReuseWindow: true });
|
||||
});
|
||||
|
||||
// Pending entry until extensions are ready
|
||||
this.renderWindowIndicator('$(sync~spin) ' + nls.localize('host.open', "Opening Remote..."), undefined, WINDOW_ACTIONS_COMMAND_ID);
|
||||
this.connectionState = 'initializing';
|
||||
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
|
||||
|
||||
MenuRegistry.appendMenuItem(MenuId.MenubarFileMenu, {
|
||||
group: '6_close',
|
||||
command: {
|
||||
id: CLOSE_REMOTE_COMMAND_ID,
|
||||
title: nls.localize({ key: 'miCloseRemote', comment: ['&& denotes a mnemonic'] }, "Close Re&&mote Connection")
|
||||
},
|
||||
order: 3.5
|
||||
});
|
||||
|
||||
const connection = remoteAgentService.getConnection();
|
||||
if (connection) {
|
||||
this._register(connection.onDidStateChange((e) => {
|
||||
switch (e.type) {
|
||||
case PersistentConnectionEventType.ConnectionLost:
|
||||
case PersistentConnectionEventType.ReconnectionPermanentFailure:
|
||||
case PersistentConnectionEventType.ReconnectionRunning:
|
||||
case PersistentConnectionEventType.ReconnectionWait:
|
||||
this.setDisconnected(true);
|
||||
break;
|
||||
case PersistentConnectionEventType.ConnectionGain:
|
||||
this.setDisconnected(false);
|
||||
break;
|
||||
}
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
extensionService.whenInstalledExtensionsRegistered().then(_ => {
|
||||
if (this.remoteAuthority) {
|
||||
this._register(this.labelService.onDidChangeFormatters(e => this.updateWindowIndicator()));
|
||||
remoteAuthorityResolverService.resolveAuthority(this.remoteAuthority).then(() => this.setDisconnected(false), () => this.setDisconnected(true));
|
||||
}
|
||||
this._register(this.windowCommandMenu.onDidChange(e => this.updateWindowActions()));
|
||||
this.updateWindowIndicator();
|
||||
});
|
||||
}
|
||||
|
||||
private setDisconnected(isDisconnected: boolean): void {
|
||||
const newState = isDisconnected ? 'disconnected' : 'connected';
|
||||
if (this.connectionState !== newState) {
|
||||
this.connectionState = newState;
|
||||
RemoteConnectionState.bindTo(this.contextKeyService).set(this.connectionState);
|
||||
Deprecated_RemoteAuthorityContext.bindTo(this.contextKeyService).set(isDisconnected ? `disconnected/${this.remoteAuthority!}` : this.remoteAuthority!);
|
||||
this.updateWindowIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private updateWindowIndicator(): void {
|
||||
const windowActionCommand = (this.remoteAuthority || this.windowCommandMenu.getActions().length) ? WINDOW_ACTIONS_COMMAND_ID : undefined;
|
||||
if (this.remoteAuthority) {
|
||||
const hostLabel = this.labelService.getHostLabel(REMOTE_HOST_SCHEME, this.remoteAuthority) || this.remoteAuthority;
|
||||
if (this.connectionState !== 'disconnected') {
|
||||
this.renderWindowIndicator(`$(remote) ${hostLabel}`, nls.localize('host.tooltip', "Editing on {0}", hostLabel), windowActionCommand);
|
||||
} else {
|
||||
this.renderWindowIndicator(`$(alert) ${nls.localize('disconnectedFrom', "Disconnected from")} ${hostLabel}`, nls.localize('host.tooltipDisconnected', "Disconnected from {0}", hostLabel), windowActionCommand);
|
||||
}
|
||||
} else {
|
||||
if (windowActionCommand) {
|
||||
this.renderWindowIndicator(`$(remote)`, nls.localize('noHost.tooltip', "Open a remote window"), windowActionCommand);
|
||||
} else if (this.windowIndicatorEntry) {
|
||||
this.windowIndicatorEntry.dispose();
|
||||
this.windowIndicatorEntry = undefined;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private updateWindowActions() {
|
||||
const newHasWindowActions = this.windowCommandMenu.getActions().length > 0;
|
||||
if (newHasWindowActions !== this.hasWindowActions) {
|
||||
this.hasWindowActions = newHasWindowActions;
|
||||
this.updateWindowIndicator();
|
||||
}
|
||||
}
|
||||
|
||||
private renderWindowIndicator(text: string, tooltip?: string, command?: string): void {
|
||||
const properties: IStatusbarEntry = {
|
||||
backgroundColor: themeColorFromId(STATUS_BAR_HOST_NAME_BACKGROUND), color: themeColorFromId(STATUS_BAR_HOST_NAME_FOREGROUND), text, tooltip, command
|
||||
};
|
||||
if (this.windowIndicatorEntry) {
|
||||
this.windowIndicatorEntry.update(properties);
|
||||
} else {
|
||||
this.windowIndicatorEntry = this.statusbarService.addEntry(properties, 'status.host', nls.localize('status.host', "Remote Host"), StatusbarAlignment.LEFT, Number.MAX_VALUE /* first entry */);
|
||||
}
|
||||
}
|
||||
|
||||
private showIndicatorActions(menu: IMenu) {
|
||||
|
||||
const actions = menu.getActions();
|
||||
|
||||
const items: (IQuickPickItem | IQuickPickSeparator)[] = [];
|
||||
for (let actionGroup of actions) {
|
||||
if (items.length) {
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
for (let action of actionGroup[1]) {
|
||||
if (action instanceof MenuItemAction) {
|
||||
let label = typeof action.item.title === 'string' ? action.item.title : action.item.title.value;
|
||||
if (action.item.category) {
|
||||
const category = typeof action.item.category === 'string' ? action.item.category : action.item.category.value;
|
||||
label = nls.localize('cat.title', "{0}: {1}", category, label);
|
||||
}
|
||||
items.push({
|
||||
type: 'item',
|
||||
id: action.item.id,
|
||||
label
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (this.remoteAuthority) {
|
||||
if (items.length) {
|
||||
items.push({ type: 'separator' });
|
||||
}
|
||||
items.push({
|
||||
type: 'item',
|
||||
id: CLOSE_REMOTE_COMMAND_ID,
|
||||
label: nls.localize('closeRemote.title', 'Close Remote Connection')
|
||||
});
|
||||
}
|
||||
|
||||
const quickPick = this.quickInputService.createQuickPick();
|
||||
quickPick.items = items;
|
||||
quickPick.canSelectMany = false;
|
||||
quickPick.onDidAccept(_ => {
|
||||
const selectedItems = quickPick.selectedItems;
|
||||
if (selectedItems.length === 1) {
|
||||
this.commandService.executeCommand(selectedItems[0].id!);
|
||||
}
|
||||
quickPick.hide();
|
||||
});
|
||||
quickPick.show();
|
||||
}
|
||||
}
|
||||
|
||||
class RemoteChannelsContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@@ -366,7 +160,6 @@ const workbenchContributionsRegistry = Registry.as<IWorkbenchContributionsRegist
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteChannelsContribution, LifecyclePhase.Starting);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteAgentDiagnosticListener, LifecyclePhase.Eventually);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteExtensionHostEnvironmentUpdater, LifecyclePhase.Eventually);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteWindowActiveIndicator, LifecyclePhase.Starting);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteTelemetryEnablementUpdater, LifecyclePhase.Ready);
|
||||
workbenchContributionsRegistry.registerWorkbenchContribution(RemoteEmptyWorkbenchPresentation, LifecyclePhase.Starting);
|
||||
|
||||
|
||||
@@ -496,6 +496,10 @@ export class AnythingQuickAccessProvider extends PickerQuickAccessProvider<IAnyt
|
||||
this.getRelativePathFileResults(query, token)
|
||||
]);
|
||||
|
||||
if (token.isCancellationRequested) {
|
||||
return [];
|
||||
}
|
||||
|
||||
// Return quickly if no relative results are present
|
||||
if (!relativePathFileResults) {
|
||||
return fileSearchResults.results.map(result => result.resource);
|
||||
|
||||
@@ -516,7 +516,7 @@ const viewContainer = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewCo
|
||||
order: 1
|
||||
}, ViewContainerLocation.Sidebar);
|
||||
|
||||
const viewDescriptor = { id: VIEW_ID, name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true };
|
||||
const viewDescriptor = { id: VIEW_ID, containerIcon: 'codicon-search', name: nls.localize('search', "Search"), ctorDescriptor: new SyncDescriptor(SearchView), canToggleVisibility: false, canMoveView: true };
|
||||
|
||||
// Register search default location to sidebar
|
||||
Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry).registerViews([viewDescriptor], viewContainer);
|
||||
|
||||
@@ -462,8 +462,8 @@ export class SearchEditor extends BaseTextEditor {
|
||||
_reason: 'searchEditor',
|
||||
extraFileResources: this.instantiationService.invokeFunction(getOutOfWorkspaceEditorResources),
|
||||
maxResults: 10000,
|
||||
disregardIgnoreFiles: !config.useIgnores,
|
||||
disregardExcludeSettings: !config.useIgnores,
|
||||
disregardIgnoreFiles: !config.useIgnores || undefined,
|
||||
disregardExcludeSettings: !config.useIgnores || undefined,
|
||||
excludePattern: config.excludes,
|
||||
includePattern: config.includes,
|
||||
previewOptions: {
|
||||
|
||||
@@ -382,7 +382,8 @@ const VIEW_CONTAINER = Registry.as<IViewContainersRegistry>(ViewContainerExtensi
|
||||
name: nls.localize('terminal', "Terminal"),
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [TERMINAL_VIEW_ID, TERMINAL_VIEW_ID, { mergeViewWithContainerWhenSingleView: true, donotShowContainerTitleWhenMergedWithContainer: true }]),
|
||||
focusCommand: { id: TERMINAL_COMMAND_ID.FOCUS },
|
||||
hideIfEmpty: true
|
||||
hideIfEmpty: true,
|
||||
order: 3
|
||||
}, ViewContainerLocation.Panel);
|
||||
Registry.as<panel.PanelRegistry>(panel.Extensions.Panels).setDefaultPanelId(TERMINAL_VIEW_ID);
|
||||
|
||||
|
||||
@@ -695,7 +695,6 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
}, {
|
||||
id: SyncResource.GlobalState,
|
||||
label: getSyncAreaLabel(SyncResource.GlobalState),
|
||||
description: localize('ui state description', "only 'Display Language' for now")
|
||||
}];
|
||||
}
|
||||
|
||||
@@ -914,7 +913,7 @@ export class UserDataSyncWorkbenchContribution extends Disposable implements IWo
|
||||
constructor() {
|
||||
super({
|
||||
id: signInCommand.id,
|
||||
title: signInCommand.title,
|
||||
title: localize('sign in 2', "Sync: Sign in to sync (1)"),
|
||||
menu: {
|
||||
group: '5_sync',
|
||||
id: MenuId.GlobalActivity,
|
||||
|
||||
@@ -7,10 +7,9 @@ import { Disposable, IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||
import { IWorkbenchContribution } from 'vs/workbench/common/contributions';
|
||||
import { IExtensionPoint } from 'vs/workbench/services/extensions/common/extensionsRegistry';
|
||||
import { ViewsWelcomeExtensionPoint, ViewWelcome, viewsWelcomeExtensionPointDescriptor, ViewIdentifierMap } from './viewsWelcomeExtensionPoint';
|
||||
import { ViewsWelcomeExtensionPoint, ViewWelcome, ViewIdentifierMap } from './viewsWelcomeExtensionPoint';
|
||||
import { Registry } from 'vs/platform/registry/common/platform';
|
||||
import { Extensions as ViewContainerExtensions, IViewsRegistry, ViewContentPriority } from 'vs/workbench/common/views';
|
||||
import { localize } from 'vs/nls';
|
||||
|
||||
const viewsRegistry = Registry.as<IViewsRegistry>(ViewContainerExtensions.ViewsRegistry);
|
||||
|
||||
@@ -23,11 +22,6 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo
|
||||
|
||||
extensionPoint.setHandler((_, { added, removed }) => {
|
||||
for (const contribution of removed) {
|
||||
// Proposed API check
|
||||
if (!contribution.description.enableProposedApi) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const welcome of contribution.value) {
|
||||
const disposable = this.viewWelcomeContents.get(welcome);
|
||||
|
||||
@@ -38,12 +32,6 @@ export class ViewsWelcomeContribution extends Disposable implements IWorkbenchCo
|
||||
}
|
||||
|
||||
for (const contribution of added) {
|
||||
// Proposed API check
|
||||
if (!contribution.description.enableProposedApi) {
|
||||
contribution.collector.error(localize('proposedAPI.invalid', "The '{0}' contribution is a proposed API and is only available when running out of dev or with the following command line switch: --enable-proposed-api {1}", viewsWelcomeExtensionPointDescriptor.extensionPoint, contribution.description.identifier.value));
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const welcome of contribution.value) {
|
||||
const id = ViewIdentifierMap[welcome.view] ?? welcome.view;
|
||||
const disposable = viewsRegistry.registerViewWelcomeContent(id, {
|
||||
|
||||
@@ -219,14 +219,14 @@ export class TitlebarPart extends BrowserTitleBarPart {
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this.title.style.zoom = null;
|
||||
this.title.style.zoom = '';
|
||||
if (isWindows || isLinux) {
|
||||
if (this.appIcon) {
|
||||
this.appIcon.style.zoom = null;
|
||||
this.appIcon.style.zoom = '';
|
||||
}
|
||||
|
||||
if (this.windowControls) {
|
||||
this.windowControls.style.zoom = null;
|
||||
this.windowControls.style.zoom = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,7 +81,7 @@ export class CommandService extends Disposable implements ICommandService {
|
||||
}
|
||||
try {
|
||||
this._onWillExecuteCommand.fire({ commandId: id, args });
|
||||
const result = this._instantiationService.invokeFunction.apply(this._instantiationService, [command.handler, ...args]);
|
||||
const result = this._instantiationService.invokeFunction(command.handler, ...args);
|
||||
this._onDidExecuteCommand.fire({ commandId: id, args });
|
||||
return Promise.resolve(result);
|
||||
} catch (err) {
|
||||
@@ -90,4 +90,4 @@ export class CommandService extends Disposable implements ICommandService {
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(ICommandService, CommandService, true);
|
||||
registerSingleton(ICommandService, CommandService, true);
|
||||
|
||||
@@ -339,7 +339,7 @@ export class HistoryService extends Disposable implements IHistoryService {
|
||||
|
||||
const navigateToStackEntry = this.navigationStack[this.navigationStackIndex];
|
||||
|
||||
this.doNavigate(navigateToStackEntry).finally(() => this.navigatingInStack = false);
|
||||
this.doNavigate(navigateToStackEntry).finally(() => { this.navigatingInStack = false; });
|
||||
}
|
||||
|
||||
private doNavigate(location: IStackEntry): Promise<IEditorPane | undefined> {
|
||||
|
||||
@@ -150,31 +150,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
// update settings schema setting for theme specific settings
|
||||
this.colorThemeRegistry.onDidChange(async event => {
|
||||
updateColorThemeConfigurationSchemas(event.themes);
|
||||
|
||||
const colorThemeSetting = this.settings.colorTheme;
|
||||
if (colorThemeSetting !== this.currentColorTheme.settingsId) {
|
||||
const theme = await this.colorThemeRegistry.findThemeBySettingsId(colorThemeSetting, undefined);
|
||||
if (theme) {
|
||||
this.setColorTheme(theme.id, undefined);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.currentColorTheme.isLoaded) {
|
||||
const themeData = await this.colorThemeRegistry.findThemeById(this.currentColorTheme.id);
|
||||
if (!themeData) {
|
||||
// current theme is no longer available
|
||||
prevColorId = this.currentColorTheme.id;
|
||||
this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto');
|
||||
} else {
|
||||
if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) {
|
||||
// restore theme
|
||||
this.setColorTheme(prevColorId, 'auto');
|
||||
prevColorId = undefined;
|
||||
} else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
|
||||
this.reloadCurrentColorTheme();
|
||||
}
|
||||
if (await this.restoreColorTheme()) { // checks if theme from settings exists and is set
|
||||
// restore theme
|
||||
if (this.currentColorTheme.id === DEFAULT_COLOR_THEME_ID && !types.isUndefined(prevColorId) && await this.colorThemeRegistry.findThemeById(prevColorId)) {
|
||||
// restore theme
|
||||
this.setColorTheme(prevColorId, 'auto');
|
||||
prevColorId = undefined;
|
||||
} else if (event.added.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
|
||||
this.reloadCurrentColorTheme();
|
||||
}
|
||||
} else if (event.removed.some(t => t.settingsId === this.currentColorTheme.settingsId)) {
|
||||
// current theme is no longer available
|
||||
prevColorId = this.currentColorTheme.id;
|
||||
this.setColorTheme(DEFAULT_COLOR_THEME_ID, 'auto');
|
||||
}
|
||||
});
|
||||
|
||||
@@ -189,7 +177,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
} else if (event.added.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
|
||||
this.reloadCurrentFileIconTheme();
|
||||
}
|
||||
} else {
|
||||
} else if (event.removed.some(t => t.settingsId === this.currentFileIconTheme.settingsId)) {
|
||||
// current theme is no longer available
|
||||
prevFileIconId = this.currentFileIconTheme.id;
|
||||
this.setFileIconTheme(DEFAULT_FILE_ICON_THEME_ID, 'auto');
|
||||
@@ -208,7 +196,7 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
} else if (event.added.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
|
||||
this.reloadCurrentProductIconTheme();
|
||||
}
|
||||
} else {
|
||||
} else if (event.removed.some(t => t.settingsId === this.currentProductIconTheme.settingsId)) {
|
||||
// current theme is no longer available
|
||||
prevProductIconId = this.currentProductIconTheme.id;
|
||||
this.setProductIconTheme(DEFAULT_PRODUCT_ICON_THEME_ID, 'auto');
|
||||
@@ -391,17 +379,19 @@ export class WorkbenchThemeService implements IWorkbenchThemeService {
|
||||
this.applyTheme(this.currentColorTheme, undefined, false);
|
||||
}
|
||||
|
||||
public restoreColorTheme() {
|
||||
const colorThemeSetting = this.settings.colorTheme;
|
||||
if (colorThemeSetting !== this.currentColorTheme.settingsId) {
|
||||
this.colorThemeRegistry.findThemeBySettingsId(colorThemeSetting, undefined).then(theme => {
|
||||
if (theme) {
|
||||
this.setColorTheme(theme.id, undefined);
|
||||
}
|
||||
});
|
||||
public async restoreColorTheme(): Promise<boolean> {
|
||||
const settingId = this.settings.colorTheme;
|
||||
const theme = await this.colorThemeRegistry.findThemeBySettingsId(settingId);
|
||||
if (theme) {
|
||||
if (settingId !== this.currentColorTheme.settingsId) {
|
||||
await this.setColorTheme(theme.id, undefined);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private updateDynamicCSSRules(themeData: IColorTheme) {
|
||||
const cssRules = new Set<string>();
|
||||
const ruleCollector = {
|
||||
|
||||
@@ -107,6 +107,7 @@ export function registerProductIconThemeExtensionPoint() {
|
||||
export interface ThemeChangeEvent<T> {
|
||||
themes: T[];
|
||||
added: T[];
|
||||
removed: T[];
|
||||
}
|
||||
|
||||
export interface IThemeData {
|
||||
@@ -135,10 +136,11 @@ export class ThemeRegistry<T extends IThemeData> {
|
||||
|
||||
private initialize() {
|
||||
this.themesExtPoint.setHandler((extensions, delta) => {
|
||||
const previousIds: { [key: string]: boolean } = {};
|
||||
const previousIds: { [key: string]: T } = {};
|
||||
|
||||
const added: T[] = [];
|
||||
for (const theme of this.extensionThemes) {
|
||||
previousIds[theme.id] = true;
|
||||
previousIds[theme.id] = theme;
|
||||
}
|
||||
this.extensionThemes.length = 0;
|
||||
for (let ext of extensions) {
|
||||
@@ -154,9 +156,12 @@ export class ThemeRegistry<T extends IThemeData> {
|
||||
for (const theme of this.extensionThemes) {
|
||||
if (!previousIds[theme.id]) {
|
||||
added.push(theme);
|
||||
} else {
|
||||
delete previousIds[theme.id];
|
||||
}
|
||||
}
|
||||
this.onDidChangeEmitter.fire({ themes: this.extensionThemes, added });
|
||||
const removed = Object.values(previousIds);
|
||||
this.onDidChangeEmitter.fire({ themes: this.extensionThemes, added, removed });
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
/*---------------------------------------------------------------------------------------------
|
||||
* Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
* Licensed under the Source EULA. See License.txt in the project root for license information.
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
import { IMainProcessService } from 'vs/platform/ipc/electron-browser/mainProcessService';
|
||||
import { StorageKeysSyncRegistryChannelClient } from 'vs/platform/userDataSync/common/userDataSyncIpc';
|
||||
|
||||
class StorageKeysSyncRegistryService extends StorageKeysSyncRegistryChannelClient implements IStorageKeysSyncRegistryService {
|
||||
|
||||
constructor(
|
||||
@IMainProcessService mainProcessService: IMainProcessService
|
||||
) {
|
||||
super(mainProcessService.getChannel('storageKeysSyncRegistryService'));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
registerSingleton(IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService);
|
||||
@@ -14,8 +14,7 @@ import { SyncDescriptor } from 'vs/platform/instantiation/common/descriptors';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
import { Event, Emitter } from 'vs/base/common/event';
|
||||
import { firstIndex } from 'vs/base/common/arrays';
|
||||
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
|
||||
import { VIEW_ID as SEARCH_VIEW_ID } from 'vs/workbench/services/search/common/search';
|
||||
import { IStorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
class CounterSet<T> implements IReadableSet<T> {
|
||||
|
||||
@@ -192,6 +191,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
private readonly viewDescriptorCollections: Map<ViewContainer, { viewDescriptorCollection: ViewDescriptorCollection, disposable: IDisposable; }>;
|
||||
private readonly activeViewContextKeys: Map<string, IContextKey<boolean>>;
|
||||
private readonly movableViewContextKeys: Map<string, IContextKey<boolean>>;
|
||||
private readonly defaultViewLocationContextKeys: Map<string, IContextKey<boolean>>;
|
||||
|
||||
private readonly viewsRegistry: IViewsRegistry;
|
||||
private readonly viewContainersRegistry: IViewContainersRegistry;
|
||||
@@ -219,13 +219,15 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
@IContextKeyService private readonly contextKeyService: IContextKeyService,
|
||||
@IStorageService private readonly storageService: IStorageService,
|
||||
@IExtensionService private readonly extensionService: IExtensionService,
|
||||
@IConfigurationService private readonly configurationService: IConfigurationService
|
||||
@IStorageKeysSyncRegistryService storageKeysSyncRegistryService: IStorageKeysSyncRegistryService
|
||||
) {
|
||||
super();
|
||||
|
||||
storageKeysSyncRegistryService.registerStorageKey({ key: ViewDescriptorService.CACHED_VIEW_POSITIONS, version: 1 });
|
||||
this.viewDescriptorCollections = new Map<ViewContainer, { viewDescriptorCollection: ViewDescriptorCollection, disposable: IDisposable; }>();
|
||||
this.activeViewContextKeys = new Map<string, IContextKey<boolean>>();
|
||||
this.movableViewContextKeys = new Map<string, IContextKey<boolean>>();
|
||||
this.defaultViewLocationContextKeys = new Map<string, IContextKey<boolean>>();
|
||||
|
||||
this.viewContainersRegistry = Registry.as<IViewContainersRegistry>(ViewExtensions.ViewContainersRegistry);
|
||||
this.viewsRegistry = Registry.as<IViewsRegistry>(ViewExtensions.ViewsRegistry);
|
||||
@@ -254,33 +256,6 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
this._register(this.storageService.onDidChangeStorage((e) => { this.onDidStorageChange(e); }));
|
||||
|
||||
this._register(this.extensionService.onDidRegisterExtensions(() => this.onDidRegisterExtensions()));
|
||||
|
||||
this._register(this.configurationService.onDidChangeConfiguration((changeEvent) => {
|
||||
if (changeEvent.affectedKeys.find(key => key === 'workbench.view.experimental.allowMovingToNewContainer')) {
|
||||
if (this.viewsCanMoveSettingValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
// update all moved views to their default locations
|
||||
for (const viewId of this.cachedViewInfo.keys()) {
|
||||
if (viewId === SEARCH_VIEW_ID) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const viewDescriptor = this.getViewDescriptor(viewId);
|
||||
const viewLocation = this.getViewContainer(viewId);
|
||||
const defaultLocation = this.getDefaultContainer(viewId);
|
||||
|
||||
if (viewDescriptor && viewLocation && defaultLocation && defaultLocation !== viewLocation) {
|
||||
this.moveViews([viewDescriptor], viewLocation, defaultLocation);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private get viewsCanMoveSettingValue(): boolean {
|
||||
return !!this.configurationService.getValue<boolean>('workbench.view.experimental.allowMovingToNewContainer');
|
||||
}
|
||||
|
||||
private registerGroupedViews(groupedViews: Map<string, { cachedContainerInfo?: ICachedViewContainerInfo, views: IViewDescriptor[] }>): void {
|
||||
@@ -377,7 +352,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
}
|
||||
|
||||
private shouldGenerateContainer(containerInfo: ICachedViewContainerInfo): boolean {
|
||||
return !!containerInfo.sourceViewId && containerInfo.location !== undefined && (this.viewsCanMoveSettingValue || containerInfo.sourceViewId === SEARCH_VIEW_ID);
|
||||
return !!containerInfo.sourceViewId && containerInfo.location !== undefined;
|
||||
}
|
||||
|
||||
private onDidDeregisterViews(views: IViewDescriptor[], viewContainer: ViewContainer): void {
|
||||
@@ -445,11 +420,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
}
|
||||
|
||||
moveViewToLocation(view: IViewDescriptor, location: ViewContainerLocation): void {
|
||||
let container = this.getDefaultContainer(view.id)!;
|
||||
if (this.getViewContainerLocation(container) !== location) {
|
||||
container = this.registerViewContainerForSingleView(view, location);
|
||||
}
|
||||
|
||||
let container = this.registerViewContainerForSingleView(view, location);
|
||||
this.moveViewsToContainer([view], container);
|
||||
}
|
||||
|
||||
@@ -491,7 +462,7 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
id,
|
||||
ctorDescriptor: new SyncDescriptor(ViewPaneContainer, [id, `${id}.state`, { mergeViewWithContainerWhenSingleView: true }]),
|
||||
name: sourceView.name,
|
||||
icon: sourceView.containerIcon,
|
||||
icon: location === ViewContainerLocation.Sidebar ? sourceView.containerIcon || 'codicon-window' : undefined,
|
||||
hideIfEmpty: true
|
||||
}, location);
|
||||
}
|
||||
@@ -675,12 +646,17 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
const sourceViewId = this.generatedContainerSourceViewIds.get(container.id);
|
||||
views.forEach(view => {
|
||||
this.cachedViewInfo.set(view.id, { containerId: container.id, location, sourceViewId });
|
||||
this.getOrCreateDefaultViewLocationContextKey(view).set(this.getDefaultContainer(view.id) === container);
|
||||
});
|
||||
|
||||
this.getViewDescriptors(container).addViews(views);
|
||||
}
|
||||
|
||||
private removeViews(container: ViewContainer, views: IViewDescriptor[]): void {
|
||||
// Set view default location keys to false
|
||||
views.forEach(view => this.getOrCreateDefaultViewLocationContextKey(view).set(false));
|
||||
|
||||
// Remove the views
|
||||
this.getViewDescriptors(container).removeViews(views);
|
||||
}
|
||||
|
||||
@@ -703,6 +679,16 @@ export class ViewDescriptorService extends Disposable implements IViewDescriptor
|
||||
}
|
||||
return contextKey;
|
||||
}
|
||||
|
||||
private getOrCreateDefaultViewLocationContextKey(viewDescriptor: IViewDescriptor): IContextKey<boolean> {
|
||||
const defaultViewLocationContextKeyId = `${viewDescriptor.id}.defaultViewLocation`;
|
||||
let contextKey = this.defaultViewLocationContextKeys.get(defaultViewLocationContextKeyId);
|
||||
if (!contextKey) {
|
||||
contextKey = new RawContextKey(defaultViewLocationContextKeyId, false).bindTo(this.contextKeyService);
|
||||
this.defaultViewLocationContextKeys.set(defaultViewLocationContextKeyId, contextKey);
|
||||
}
|
||||
return contextKey;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IViewDescriptorService, ViewDescriptorService);
|
||||
|
||||
@@ -535,8 +535,8 @@ suite('ViewDescriptorService', () => {
|
||||
sidebarViews = viewDescriptorService.getViewDescriptors(sidebarContainer);
|
||||
panelViews = viewDescriptorService.getViewDescriptors(panelContainer);
|
||||
|
||||
assert.equal(sidebarViews.activeViewDescriptors.length, 2, 'Sidebar should have 2 views');
|
||||
assert.equal(panelViews.activeViewDescriptors.length, 1, 'Panel should have 1 view');
|
||||
assert.equal(sidebarViews.activeViewDescriptors.length, 1, 'Sidebar should have 2 views');
|
||||
assert.equal(panelViews.activeViewDescriptors.length, 0, 'Panel should have 1 view');
|
||||
|
||||
assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[0].id), ViewContainerLocation.Sidebar, 'View should be located in the sidebar');
|
||||
assert.equal(viewDescriptorService.getViewLocation(viewDescriptors[2].id), ViewContainerLocation.Panel, 'View should be located in the panel');
|
||||
@@ -602,11 +602,11 @@ suite('ViewDescriptorService', () => {
|
||||
|
||||
expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Panel, ViewContainerLocation.Sidebar);
|
||||
expectedSequence += containerMoveString(viewDescriptors[0], viewDescriptorService.getViewContainer(viewDescriptors[0].id)!, sidebarContainer);
|
||||
viewDescriptorService.moveViewToLocation(viewDescriptors[0], ViewContainerLocation.Sidebar);
|
||||
viewDescriptorService.moveViewsToContainer([viewDescriptors[0]], sidebarContainer);
|
||||
|
||||
expectedSequence += locationMoveString(viewDescriptors[2], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel);
|
||||
expectedSequence += containerMoveString(viewDescriptors[2], viewDescriptorService.getViewContainer(viewDescriptors[2].id)!, panelContainer);
|
||||
viewDescriptorService.moveViewToLocation(viewDescriptors[2], ViewContainerLocation.Panel);
|
||||
viewDescriptorService.moveViewsToContainer([viewDescriptors[2]], panelContainer);
|
||||
|
||||
expectedSequence += locationMoveString(viewDescriptors[0], ViewContainerLocation.Sidebar, ViewContainerLocation.Panel);
|
||||
expectedSequence += containerMoveString(viewDescriptors[0], sidebarContainer, panelContainer);
|
||||
|
||||
@@ -105,6 +105,7 @@ import { IListService } from 'vs/platform/list/browser/listService';
|
||||
import { win32, posix } from 'vs/base/common/path';
|
||||
import { TestWorkingCopyService, TestContextService, TestStorageService, TestTextResourcePropertiesService } from 'vs/workbench/test/common/workbenchTestServices';
|
||||
import { IViewsService, IView } from 'vs/workbench/common/views';
|
||||
import { IStorageKeysSyncRegistryService, StorageKeysSyncRegistryService } from 'vs/platform/userDataSync/common/storageKeys';
|
||||
|
||||
export function createFileEditorInput(instantiationService: IInstantiationService, resource: URI): FileEditorInput {
|
||||
return instantiationService.createInstance(FileEditorInput, resource, undefined, undefined);
|
||||
@@ -168,6 +169,7 @@ export function workbenchInstantiationService(overrides?: { textFileService?: (i
|
||||
instantiationService.stub(IViewletService, new TestViewletService());
|
||||
instantiationService.stub(IListService, new TestListService());
|
||||
instantiationService.stub(IQuickInputService, new QuickInputService(TestEnvironmentService, configService, instantiationService, keybindingService, contextKeyService, themeService, accessibilityService, layoutService));
|
||||
instantiationService.stub(IStorageKeysSyncRegistryService, new StorageKeysSyncRegistryService());
|
||||
|
||||
return instantiationService;
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@ import 'vs/workbench/services/backup/node/backupFileService';
|
||||
import 'vs/workbench/services/url/electron-browser/urlService';
|
||||
import 'vs/workbench/services/workspaces/electron-browser/workspacesService';
|
||||
import 'vs/workbench/services/workspaces/electron-browser/workspaceEditingService';
|
||||
import 'vs/workbench/services/userDataSync/electron-browser/storageKeysSyncRegistryService';
|
||||
import 'vs/workbench/services/userDataSync/electron-browser/userDataSyncService';
|
||||
import 'vs/workbench/services/userDataSync/electron-browser/userDataAutoSyncService';
|
||||
import 'vs/workbench/services/authentication/electron-browser/authenticationTokenService';
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user