mirror of
https://github.com/Microsoft/vscode
synced 2024-10-12 22:37:41 +00:00
Merge remote-tracking branch 'upstream/master' into rebornix/viewportevent
This commit is contained in:
commit
18f2fd24c4
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "code-oss-dev",
|
"name": "code-oss-dev",
|
||||||
"version": "1.41.0",
|
"version": "1.41.0",
|
||||||
"distro": "403ab44be562c63a0cde1969fd8f5b45ff51709c",
|
"distro": "e3f0dacfbf82a87e6a3e826d18282d02a67c25c7",
|
||||||
"author": {
|
"author": {
|
||||||
"name": "Microsoft Corporation"
|
"name": "Microsoft Corporation"
|
||||||
},
|
},
|
||||||
|
@ -38,6 +38,7 @@
|
||||||
"iconv-lite": "0.5.0",
|
"iconv-lite": "0.5.0",
|
||||||
"jschardet": "2.1.1",
|
"jschardet": "2.1.1",
|
||||||
"keytar": "^4.11.0",
|
"keytar": "^4.11.0",
|
||||||
|
"msal": "^1.1.3",
|
||||||
"native-is-elevated": "0.4.1",
|
"native-is-elevated": "0.4.1",
|
||||||
"native-keymap": "2.0.0",
|
"native-keymap": "2.0.0",
|
||||||
"native-watchdog": "1.2.0",
|
"native-watchdog": "1.2.0",
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
"https-proxy-agent": "^2.2.3",
|
"https-proxy-agent": "^2.2.3",
|
||||||
"iconv-lite": "0.5.0",
|
"iconv-lite": "0.5.0",
|
||||||
"jschardet": "2.1.1",
|
"jschardet": "2.1.1",
|
||||||
|
"msal": "^1.1.3",
|
||||||
"native-watchdog": "1.2.0",
|
"native-watchdog": "1.2.0",
|
||||||
"node-pty": "^0.10.0-beta2",
|
"node-pty": "^0.10.0-beta2",
|
||||||
"onigasm-umd": "^2.2.4",
|
"onigasm-umd": "^2.2.4",
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"name": "vscode-web",
|
"name": "vscode-web",
|
||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"msal": "^1.1.3",
|
||||||
"onigasm-umd": "^2.2.4",
|
"onigasm-umd": "^2.2.4",
|
||||||
"semver-umd": "^5.5.3",
|
"semver-umd": "^5.5.3",
|
||||||
"vscode-textmate": "^4.3.0",
|
"vscode-textmate": "^4.3.0",
|
||||||
|
|
|
@ -2,6 +2,13 @@
|
||||||
# yarn lockfile v1
|
# yarn lockfile v1
|
||||||
|
|
||||||
|
|
||||||
|
msal@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/msal/-/msal-1.1.3.tgz#d8c501d513f5e52eaf000f20a245526fc1ecaa2a"
|
||||||
|
integrity sha512-cdShb+N1H3OyR1y46ij6OO7QzeqC6BxrbrNcouS4JBrr1+DnZ55TumxQKEzWmTXHvsbsuz5PCyXZl812Un8L9g==
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
nan@^2.14.0:
|
nan@^2.14.0:
|
||||||
version "2.14.0"
|
version "2.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||||
|
@ -24,6 +31,11 @@ semver-umd@^5.5.3:
|
||||||
resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e"
|
resolved "https://registry.yarnpkg.com/semver-umd/-/semver-umd-5.5.3.tgz#b64d7a2d4f5a717b369d56e31940a38e47e34d1e"
|
||||||
integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw==
|
integrity sha512-HOnQrn2iKnVe/xlqCTzMXQdvSz3rPbD0DmQXYuQ+oK1dpptGFfPghonQrx5JHl2O7EJwDqtQnjhE7ME23q6ngw==
|
||||||
|
|
||||||
|
tslib@^1.9.3:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
|
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||||
|
|
||||||
vscode-textmate@^4.3.0:
|
vscode-textmate@^4.3.0:
|
||||||
version "4.3.0"
|
version "4.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.3.0.tgz#6e1f0f273d84148cfa1e9c7ed85bd16c974f9f61"
|
resolved "https://registry.yarnpkg.com/vscode-textmate/-/vscode-textmate-4.3.0.tgz#6e1f0f273d84148cfa1e9c7ed85bd16c974f9f61"
|
||||||
|
|
|
@ -256,6 +256,13 @@ ms@2.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"
|
||||||
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=
|
||||||
|
|
||||||
|
msal@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/msal/-/msal-1.1.3.tgz#d8c501d513f5e52eaf000f20a245526fc1ecaa2a"
|
||||||
|
integrity sha512-cdShb+N1H3OyR1y46ij6OO7QzeqC6BxrbrNcouS4JBrr1+DnZ55TumxQKEzWmTXHvsbsuz5PCyXZl812Un8L9g==
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
nan@^2.10.0, nan@^2.14.0:
|
nan@^2.10.0, nan@^2.14.0:
|
||||||
version "2.14.0"
|
version "2.14.0"
|
||||||
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
resolved "https://registry.yarnpkg.com/nan/-/nan-2.14.0.tgz#7818f722027b2459a86f0295d434d1fc2336c52c"
|
||||||
|
@ -364,6 +371,11 @@ to-regex-range@^5.0.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
is-number "^7.0.0"
|
is-number "^7.0.0"
|
||||||
|
|
||||||
|
tslib@^1.9.3:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
|
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||||
|
|
||||||
universalify@^0.1.0:
|
universalify@^0.1.0:
|
||||||
version "0.1.2"
|
version "0.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||||
|
|
|
@ -56,5 +56,5 @@ export const BrowserFeatures = {
|
||||||
})(),
|
})(),
|
||||||
|
|
||||||
touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0,
|
touch: 'ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0,
|
||||||
pointerEvents: browser.isSafari && window.PointerEvent && ('ontouchstart' in window || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0)
|
pointerEvents: window.PointerEvent && ('ontouchstart' in window || window.navigator.maxTouchPoints > 0 || navigator.maxTouchPoints > 0 || window.navigator.msMaxTouchPoints > 0)
|
||||||
};
|
};
|
||||||
|
|
|
@ -267,6 +267,13 @@ export let addStandardDisposableListener: IAddStandardDisposableListenerSignatur
|
||||||
return addDisposableListener(node, type, wrapHandler, useCapture);
|
return addDisposableListener(node, type, wrapHandler, useCapture);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function addDisposableGenericMouseDownListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
|
||||||
|
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_DOWN : EventType.MOUSE_DOWN, handler, useCapture);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function addDisposableGenericMouseUpListner(node: EventTarget, handler: (event: any) => void, useCapture?: boolean): IDisposable {
|
||||||
|
return addDisposableListener(node, platform.isIOS && BrowserFeatures.pointerEvents ? EventType.POINTER_UP : EventType.MOUSE_UP, handler, useCapture);
|
||||||
|
}
|
||||||
export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
|
export function addDisposableNonBubblingMouseOutListener(node: Element, handler: (event: MouseEvent) => void): IDisposable {
|
||||||
return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
|
return addDisposableListener(node, 'mouseout', (e: MouseEvent) => {
|
||||||
// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
|
// Mouse out bubbles, so this is an attempt to ignore faux mouse outs coming from children elements
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as dom from 'vs/base/browser/dom';
|
import * as dom from 'vs/base/browser/dom';
|
||||||
|
import * as platform from 'vs/base/common/platform';
|
||||||
import { IframeUtils } from 'vs/base/browser/iframe';
|
import { IframeUtils } from 'vs/base/browser/iframe';
|
||||||
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
import { StandardMouseEvent } from 'vs/base/browser/mouseEvent';
|
||||||
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
import { IDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
|
@ -85,8 +86,8 @@ export class GlobalMouseMoveMonitor<R> implements IDisposable {
|
||||||
this.onStopCallback = onStopCallback;
|
this.onStopCallback = onStopCallback;
|
||||||
|
|
||||||
let windowChain = IframeUtils.getSameOriginWindowChain();
|
let windowChain = IframeUtils.getSameOriginWindowChain();
|
||||||
const mouseMove = BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
|
const mouseMove = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointermove' : 'mousemove';
|
||||||
const mouseUp = BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
|
const mouseUp = platform.isIOS && BrowserFeatures.pointerEvents ? 'pointerup' : 'mouseup';
|
||||||
for (const element of windowChain) {
|
for (const element of windowChain) {
|
||||||
this.hooks.add(dom.addDisposableThrottledListener(element.window.document, mouseMove,
|
this.hooks.add(dom.addDisposableThrottledListener(element.window.document, mouseMove,
|
||||||
(data: R) => this.mouseMoveCallback!(data),
|
(data: R) => this.mouseMoveCallback!(data),
|
||||||
|
|
|
@ -5,6 +5,7 @@
|
||||||
|
|
||||||
import 'vs/css!./contextview';
|
import 'vs/css!./contextview';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
|
import * as platform from 'vs/base/common/platform';
|
||||||
import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
import { IDisposable, toDisposable, Disposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { Range } from 'vs/base/common/range';
|
import { Range } from 'vs/base/common/range';
|
||||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||||
|
@ -179,7 +180,7 @@ export class ContextView extends Disposable {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.delegate!.canRelayout === false && !BrowserFeatures.pointerEvents) {
|
if (this.delegate!.canRelayout === false && !(platform.isIOS && BrowserFeatures.pointerEvents)) {
|
||||||
this.hide();
|
this.hide();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,6 +8,7 @@ import * as dom from 'vs/base/browser/dom';
|
||||||
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
import { HighlightedLabel } from 'vs/base/browser/ui/highlightedlabel/highlightedLabel';
|
||||||
import { IMatch } from 'vs/base/common/filters';
|
import { IMatch } from 'vs/base/common/filters';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
import { Range } from 'vs/base/common/range';
|
||||||
|
|
||||||
export interface IIconLabelCreationOptions {
|
export interface IIconLabelCreationOptions {
|
||||||
supportHighlights?: boolean;
|
supportHighlights?: boolean;
|
||||||
|
@ -24,6 +25,7 @@ export interface IIconLabelValueOptions {
|
||||||
matches?: IMatch[];
|
matches?: IMatch[];
|
||||||
labelEscapeNewLines?: boolean;
|
labelEscapeNewLines?: boolean;
|
||||||
descriptionMatches?: IMatch[];
|
descriptionMatches?: IMatch[];
|
||||||
|
readonly separator?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
class FastLabelNode {
|
class FastLabelNode {
|
||||||
|
@ -86,9 +88,10 @@ class FastLabelNode {
|
||||||
}
|
}
|
||||||
|
|
||||||
export class IconLabel extends Disposable {
|
export class IconLabel extends Disposable {
|
||||||
|
|
||||||
private domNode: FastLabelNode;
|
private domNode: FastLabelNode;
|
||||||
private labelDescriptionContainer: FastLabelNode;
|
private descriptionContainer: FastLabelNode;
|
||||||
private labelNode: FastLabelNode | HighlightedLabel;
|
private nameNode: Label | LabelWithHighlights;
|
||||||
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
|
private descriptionNode: FastLabelNode | HighlightedLabel | undefined;
|
||||||
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
private descriptionNodeFactory: () => FastLabelNode | HighlightedLabel;
|
||||||
|
|
||||||
|
@ -97,18 +100,21 @@ export class IconLabel extends Disposable {
|
||||||
|
|
||||||
this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
|
this.domNode = this._register(new FastLabelNode(dom.append(container, dom.$('.monaco-icon-label'))));
|
||||||
|
|
||||||
this.labelDescriptionContainer = this._register(new FastLabelNode(dom.append(this.domNode.element, dom.$('.monaco-icon-label-description-container'))));
|
const labelContainer = dom.append(this.domNode.element, dom.$('.monaco-icon-label-container'));
|
||||||
|
|
||||||
|
const nameContainer = dom.append(labelContainer, dom.$('span.monaco-icon-name-container'));
|
||||||
|
this.descriptionContainer = this._register(new FastLabelNode(dom.append(labelContainer, dom.$('span.monaco-icon-description-container'))));
|
||||||
|
|
||||||
if (options?.supportHighlights) {
|
if (options?.supportHighlights) {
|
||||||
this.labelNode = new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name')), !!options.supportCodicons);
|
this.nameNode = new LabelWithHighlights(nameContainer, !!options.supportCodicons);
|
||||||
} else {
|
} else {
|
||||||
this.labelNode = this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('a.label-name'))));
|
this.nameNode = new Label(nameContainer);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (options?.supportDescriptionHighlights) {
|
if (options?.supportDescriptionHighlights) {
|
||||||
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
|
this.descriptionNodeFactory = () => new HighlightedLabel(dom.append(this.descriptionContainer.element, dom.$('span.label-description')), !!options.supportCodicons);
|
||||||
} else {
|
} else {
|
||||||
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.labelDescriptionContainer.element, dom.$('span.label-description'))));
|
this.descriptionNodeFactory = () => this._register(new FastLabelNode(dom.append(this.descriptionContainer.element, dom.$('span.label-description'))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,7 +122,7 @@ export class IconLabel extends Disposable {
|
||||||
return this.domNode.element;
|
return this.domNode.element;
|
||||||
}
|
}
|
||||||
|
|
||||||
setLabel(label: string, description?: string, options?: IIconLabelValueOptions): void {
|
setLabel(label: string | string[], description?: string, options?: IIconLabelValueOptions): void {
|
||||||
const classes = ['monaco-icon-label'];
|
const classes = ['monaco-icon-label'];
|
||||||
if (options) {
|
if (options) {
|
||||||
if (options.extraClasses) {
|
if (options.extraClasses) {
|
||||||
|
@ -131,11 +137,7 @@ export class IconLabel extends Disposable {
|
||||||
this.domNode.className = classes.join(' ');
|
this.domNode.className = classes.join(' ');
|
||||||
this.domNode.title = options?.title || '';
|
this.domNode.title = options?.title || '';
|
||||||
|
|
||||||
if (this.labelNode instanceof HighlightedLabel) {
|
this.nameNode.setLabel(label, options);
|
||||||
this.labelNode.set(label || '', options?.matches, options?.title, options?.labelEscapeNewLines);
|
|
||||||
} else {
|
|
||||||
this.labelNode.textContent = label || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (description || this.descriptionNode) {
|
if (description || this.descriptionNode) {
|
||||||
if (!this.descriptionNode) {
|
if (!this.descriptionNode) {
|
||||||
|
@ -157,3 +159,110 @@ export class IconLabel extends Disposable {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class Label {
|
||||||
|
|
||||||
|
private label: string | string[] | undefined = undefined;
|
||||||
|
private singleLabel: HTMLElement | undefined = undefined;
|
||||||
|
|
||||||
|
constructor(private container: HTMLElement) { }
|
||||||
|
|
||||||
|
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
|
||||||
|
if (this.label === label) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.label = label;
|
||||||
|
|
||||||
|
if (typeof label === 'string') {
|
||||||
|
if (!this.singleLabel) {
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
dom.removeClass(this.container, 'multiple');
|
||||||
|
this.singleLabel = dom.append(this.container, dom.$('a.label-name'));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.singleLabel.textContent = label;
|
||||||
|
} else {
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
dom.addClass(this.container, 'multiple');
|
||||||
|
this.singleLabel = undefined;
|
||||||
|
|
||||||
|
for (let i = 0; i < label.length; i++) {
|
||||||
|
const l = label[i];
|
||||||
|
|
||||||
|
dom.append(this.container, dom.$('a.label-name', { 'data-icon-label-count': label.length, 'data-icon-label-index': i }, l));
|
||||||
|
|
||||||
|
if (i < label.length - 1) {
|
||||||
|
dom.append(this.container, dom.$('span.label-separator', undefined, options?.separator || '/'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function splitMatches(labels: string[], separator: string, matches: IMatch[] | undefined): IMatch[][] | undefined {
|
||||||
|
if (!matches) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
let labelStart = 0;
|
||||||
|
|
||||||
|
return labels.map(label => {
|
||||||
|
const labelRange = { start: labelStart, end: labelStart + label.length };
|
||||||
|
|
||||||
|
const result = matches
|
||||||
|
.map(match => Range.intersect(labelRange, match))
|
||||||
|
.filter(range => !Range.isEmpty(range))
|
||||||
|
.map(({ start, end }) => ({ start: start - labelStart, end: end - labelStart }));
|
||||||
|
|
||||||
|
labelStart = labelRange.end + separator.length;
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
class LabelWithHighlights {
|
||||||
|
|
||||||
|
private label: string | string[] | undefined = undefined;
|
||||||
|
private singleLabel: HighlightedLabel | undefined = undefined;
|
||||||
|
|
||||||
|
constructor(private container: HTMLElement, private supportCodicons: boolean) { }
|
||||||
|
|
||||||
|
setLabel(label: string | string[], options?: IIconLabelValueOptions): void {
|
||||||
|
if (this.label === label) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.label = label;
|
||||||
|
|
||||||
|
if (typeof label === 'string') {
|
||||||
|
if (!this.singleLabel) {
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
dom.removeClass(this.container, 'multiple');
|
||||||
|
this.singleLabel = new HighlightedLabel(dom.append(this.container, dom.$('a.label-name')), this.supportCodicons);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.singleLabel.set(label, options?.matches, options?.title, options?.labelEscapeNewLines);
|
||||||
|
} else {
|
||||||
|
|
||||||
|
this.container.innerHTML = '';
|
||||||
|
dom.addClass(this.container, 'multiple');
|
||||||
|
this.singleLabel = undefined;
|
||||||
|
|
||||||
|
const separator = options?.separator || '/';
|
||||||
|
const matches = splitMatches(label, separator, options?.matches);
|
||||||
|
|
||||||
|
for (let i = 0; i < label.length; i++) {
|
||||||
|
const l = label[i];
|
||||||
|
const m = matches ? matches[i] : undefined;
|
||||||
|
|
||||||
|
const name = dom.$('a.label-name', { 'data-icon-label-count': label.length, 'data-icon-label-index': i });
|
||||||
|
const highlightedLabel = new HighlightedLabel(dom.append(this.container, name), this.supportCodicons);
|
||||||
|
highlightedLabel.set(l, m, options?.title, options?.labelEscapeNewLines);
|
||||||
|
|
||||||
|
if (i < label.length - 1) {
|
||||||
|
dom.append(name, dom.$('span.label-separator', undefined, separator));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -31,25 +31,33 @@
|
||||||
flex-shrink: 0; /* fix for https://github.com/Microsoft/vscode/issues/13787 */
|
flex-shrink: 0; /* fix for https://github.com/Microsoft/vscode/issues/13787 */
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-icon-label > .monaco-icon-label-description-container {
|
.monaco-icon-label > .monaco-icon-label-container {
|
||||||
overflow: hidden; /* this causes the label/description to shrink first if decorations are enabled */
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
text-overflow: ellipsis;
|
text-overflow: ellipsis;
|
||||||
|
flex: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-icon-label > .monaco-icon-label-description-container > .label-name {
|
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name {
|
||||||
color: inherit;
|
color: inherit;
|
||||||
white-space: pre; /* enable to show labels that include multiple whitespaces */
|
white-space: pre; /* enable to show labels that include multiple whitespaces */
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-icon-label > .monaco-icon-label-description-container > .label-description {
|
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-name-container > .label-name > .label-separator {
|
||||||
|
margin-left: 3px;
|
||||||
|
margin-right: 1px;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
|
||||||
|
.monaco-icon-label > .monaco-icon-label-container > .monaco-icon-description-container > .label-description {
|
||||||
opacity: .7;
|
opacity: .7;
|
||||||
margin-left: 0.5em;
|
margin-left: 0.5em;
|
||||||
font-size: 0.9em;
|
font-size: 0.9em;
|
||||||
white-space: pre; /* enable to show labels that include multiple whitespaces */
|
white-space: pre; /* enable to show labels that include multiple whitespaces */
|
||||||
}
|
}
|
||||||
|
|
||||||
.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-name,
|
.monaco-icon-label.italic > .monaco-icon-label-container > .monaco-icon-name-container > .label-name,
|
||||||
.monaco-icon-label.italic > .monaco-icon-label-description-container > .label-description {
|
.monaco-icon-label.italic > .monaco-icon-description-container > .label-description {
|
||||||
font-style: italic;
|
font-style: italic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -58,7 +66,6 @@
|
||||||
font-size: 90%;
|
font-size: 90%;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
padding: 0 16px 0 5px;
|
padding: 0 16px 0 5px;
|
||||||
margin-left: auto;
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -103,10 +103,11 @@ export const ListDragOverReactions = {
|
||||||
|
|
||||||
export interface IListDragAndDrop<T> {
|
export interface IListDragAndDrop<T> {
|
||||||
getDragURI(element: T): string | null;
|
getDragURI(element: T): string | null;
|
||||||
getDragLabel?(elements: T[]): string | undefined;
|
getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined;
|
||||||
onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
|
onDragStart?(data: IDragAndDropData, originalEvent: DragEvent): void;
|
||||||
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
|
onDragOver(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | IListDragOverReaction;
|
||||||
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
|
drop(data: IDragAndDropData, targetElement: T | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void;
|
||||||
|
onDragEnd?(originalEvent: DragEvent): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ListError extends Error {
|
export class ListError extends Error {
|
||||||
|
|
|
@ -74,9 +74,10 @@ const DefaultOptions = {
|
||||||
horizontalScrolling: false
|
horizontalScrolling: false
|
||||||
};
|
};
|
||||||
|
|
||||||
export class ElementsDragAndDropData<T> implements IDragAndDropData {
|
export class ElementsDragAndDropData<T, TContext = void> implements IDragAndDropData {
|
||||||
|
|
||||||
readonly elements: T[];
|
readonly elements: T[];
|
||||||
|
context: TContext | undefined;
|
||||||
|
|
||||||
constructor(elements: T[]) {
|
constructor(elements: T[]) {
|
||||||
this.elements = elements;
|
this.elements = elements;
|
||||||
|
@ -84,7 +85,7 @@ export class ElementsDragAndDropData<T> implements IDragAndDropData {
|
||||||
|
|
||||||
update(): void { }
|
update(): void { }
|
||||||
|
|
||||||
getData(): any {
|
getData(): T[] {
|
||||||
return this.elements;
|
return this.elements;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -99,7 +100,7 @@ export class ExternalElementsDragAndDropData<T> implements IDragAndDropData {
|
||||||
|
|
||||||
update(): void { }
|
update(): void { }
|
||||||
|
|
||||||
getData(): any {
|
getData(): T[] {
|
||||||
return this.elements;
|
return this.elements;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -766,7 +767,7 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||||
let label: string | undefined;
|
let label: string | undefined;
|
||||||
|
|
||||||
if (this.dnd.getDragLabel) {
|
if (this.dnd.getDragLabel) {
|
||||||
label = this.dnd.getDragLabel(elements);
|
label = this.dnd.getDragLabel(elements, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof label === 'undefined') {
|
if (typeof label === 'undefined') {
|
||||||
|
@ -846,10 +847,6 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||||
feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
|
feedback = distinct(feedback).filter(i => i >= -1 && i < this.length).sort();
|
||||||
feedback = feedback[0] === -1 ? [-1] : feedback;
|
feedback = feedback[0] === -1 ? [-1] : feedback;
|
||||||
|
|
||||||
if (feedback.length === 0) {
|
|
||||||
throw new Error('Invalid empty feedback list');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
|
if (equalsDragFeedback(this.currentDragFeedback, feedback)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -910,12 +907,16 @@ export class ListView<T> implements ISpliceable<T>, IDisposable {
|
||||||
this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
|
this.dnd.drop(dragData, event.element, event.index, event.browserEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private onDragEnd(): void {
|
private onDragEnd(event: DragEvent): void {
|
||||||
this.canDrop = false;
|
this.canDrop = false;
|
||||||
this.teardownDragAndDropScrollTopAnimation();
|
this.teardownDragAndDropScrollTopAnimation();
|
||||||
this.clearDragOverFeedback();
|
this.clearDragOverFeedback();
|
||||||
this.currentDragData = undefined;
|
this.currentDragData = undefined;
|
||||||
StaticDND.CurrentDragAndDropData = undefined;
|
StaticDND.CurrentDragAndDropData = undefined;
|
||||||
|
|
||||||
|
if (this.dnd.onDragEnd) {
|
||||||
|
this.dnd.onDragEnd(event);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private clearDragOverFeedback(): void {
|
private clearDragOverFeedback(): void {
|
||||||
|
|
|
@ -1062,9 +1062,9 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||||
return this.dnd.getDragURI(element);
|
return this.dnd.getDragURI(element);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDragLabel?(elements: T[]): string | undefined {
|
getDragLabel?(elements: T[], originalEvent: DragEvent): string | undefined {
|
||||||
if (this.dnd.getDragLabel) {
|
if (this.dnd.getDragLabel) {
|
||||||
return this.dnd.getDragLabel(elements);
|
return this.dnd.getDragLabel(elements, originalEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -1080,6 +1080,12 @@ class ListViewDragAndDrop<T> implements IListViewDragAndDrop<T> {
|
||||||
return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
|
return this.dnd.onDragOver(data, targetElement, targetIndex, originalEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDragEnd(originalEvent: DragEvent): void {
|
||||||
|
if (this.dnd.onDragEnd) {
|
||||||
|
this.dnd.onDragEnd(originalEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
|
drop(data: IDragAndDropData, targetElement: T, targetIndex: number, originalEvent: DragEvent): void {
|
||||||
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
|
this.dnd.drop(data, targetElement, targetIndex, originalEvent);
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,10 +27,24 @@ import { clamp } from 'vs/base/common/numbers';
|
||||||
import { ScrollEvent } from 'vs/base/common/scrollable';
|
import { ScrollEvent } from 'vs/base/common/scrollable';
|
||||||
import { SetMap } from 'vs/base/common/collections';
|
import { SetMap } from 'vs/base/common/collections';
|
||||||
|
|
||||||
|
class TreeElementsDragAndDropData<T, TFilterData, TContext> extends ElementsDragAndDropData<T, TContext> {
|
||||||
|
|
||||||
|
set context(context: TContext | undefined) {
|
||||||
|
this.data.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
get context(): TContext | undefined {
|
||||||
|
return this.data.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private data: ElementsDragAndDropData<ITreeNode<T, TFilterData>, TContext>) {
|
||||||
|
super(data.elements.map(node => node.element));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function asTreeDragAndDropData<T, TFilterData>(data: IDragAndDropData): IDragAndDropData {
|
function asTreeDragAndDropData<T, TFilterData>(data: IDragAndDropData): IDragAndDropData {
|
||||||
if (data instanceof ElementsDragAndDropData) {
|
if (data instanceof ElementsDragAndDropData) {
|
||||||
const nodes = (data as ElementsDragAndDropData<ITreeNode<T, TFilterData>>).elements;
|
return new TreeElementsDragAndDropData(data);
|
||||||
return new ElementsDragAndDropData(nodes.map(node => node.element));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -47,9 +61,9 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||||
return this.dnd.getDragURI(node.element);
|
return this.dnd.getDragURI(node.element);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDragLabel(nodes: ITreeNode<T, TFilterData>[]): string | undefined {
|
getDragLabel(nodes: ITreeNode<T, TFilterData>[], originalEvent: DragEvent): string | undefined {
|
||||||
if (this.dnd.getDragLabel) {
|
if (this.dnd.getDragLabel) {
|
||||||
return this.dnd.getDragLabel(nodes.map(node => node.element));
|
return this.dnd.getDragLabel(nodes.map(node => node.element), originalEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -87,7 +101,7 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined') {
|
if (typeof result === 'boolean' || !result.accept || typeof result.bubble === 'undefined' || result.feedback) {
|
||||||
if (!raw) {
|
if (!raw) {
|
||||||
const accept = typeof result === 'boolean' ? result : result.accept;
|
const accept = typeof result === 'boolean' ? result : result.accept;
|
||||||
const effect = typeof result === 'boolean' ? undefined : result.effect;
|
const effect = typeof result === 'boolean' ? undefined : result.effect;
|
||||||
|
@ -121,6 +135,12 @@ class TreeNodeListDragAndDrop<T, TFilterData, TRef> implements IListDragAndDrop<
|
||||||
|
|
||||||
this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
|
this.dnd.drop(asTreeDragAndDropData(data), targetNode && targetNode.element, targetIndex, originalEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDragEnd(originalEvent: DragEvent): void {
|
||||||
|
if (this.dnd.onDragEnd) {
|
||||||
|
this.dnd.onDragEnd(originalEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
|
function asListOptions<T, TFilterData, TRef>(modelProvider: () => ITreeModel<T, TFilterData, TRef>, options?: IAbstractTreeOptions<T, TFilterData>): IListOptions<ITreeNode<T, TFilterData>> | undefined {
|
||||||
|
|
|
@ -150,10 +150,24 @@ function asTreeContextMenuEvent<TInput, T>(e: ITreeContextMenuEvent<IAsyncDataTr
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class AsyncDataTreeElementsDragAndDropData<TInput, T, TContext> extends ElementsDragAndDropData<T, TContext> {
|
||||||
|
|
||||||
|
set context(context: TContext | undefined) {
|
||||||
|
this.data.context = context;
|
||||||
|
}
|
||||||
|
|
||||||
|
get context(): TContext | undefined {
|
||||||
|
return this.data.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(private data: ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>, TContext>) {
|
||||||
|
super(data.elements.map(node => node.element as T));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
|
function asAsyncDataTreeDragAndDropData<TInput, T>(data: IDragAndDropData): IDragAndDropData {
|
||||||
if (data instanceof ElementsDragAndDropData) {
|
if (data instanceof ElementsDragAndDropData) {
|
||||||
const nodes = (data as ElementsDragAndDropData<IAsyncDataTreeNode<TInput, T>>).elements;
|
return new AsyncDataTreeElementsDragAndDropData(data);
|
||||||
return new ElementsDragAndDropData(nodes.map(node => node.element));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return data;
|
return data;
|
||||||
|
@ -167,9 +181,9 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
||||||
return this.dnd.getDragURI(node.element as T);
|
return this.dnd.getDragURI(node.element as T);
|
||||||
}
|
}
|
||||||
|
|
||||||
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[]): string | undefined {
|
getDragLabel(nodes: IAsyncDataTreeNode<TInput, T>[], originalEvent: DragEvent): string | undefined {
|
||||||
if (this.dnd.getDragLabel) {
|
if (this.dnd.getDragLabel) {
|
||||||
return this.dnd.getDragLabel(nodes.map(node => node.element as T));
|
return this.dnd.getDragLabel(nodes.map(node => node.element as T), originalEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
return undefined;
|
return undefined;
|
||||||
|
@ -188,6 +202,12 @@ class AsyncDataTreeNodeListDragAndDrop<TInput, T> implements IListDragAndDrop<IA
|
||||||
drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
|
drop(data: IDragAndDropData, targetNode: IAsyncDataTreeNode<TInput, T> | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
|
||||||
this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
|
this.dnd.drop(asAsyncDataTreeDragAndDropData(data), targetNode && targetNode.element as T, targetIndex, originalEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onDragEnd(originalEvent: DragEvent): void {
|
||||||
|
if (this.dnd.onDragEnd) {
|
||||||
|
this.dnd.onDragEnd(originalEvent);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
|
function asObjectTreeOptions<TInput, T, TFilterData>(options?: IAsyncDataTreeOptions<T, TFilterData>): IObjectTreeOptions<IAsyncDataTreeNode<TInput, T>, TFilterData> | undefined {
|
||||||
|
@ -993,6 +1013,12 @@ class CompressibleAsyncDataTreeRenderer<TInput, T, TFilterData, TTemplateData> i
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<IAsyncDataTreeNode<TInput, T>>, TFilterData>, index: number, templateData: IDataTreeListTemplateData<TTemplateData>, height: number | undefined): void {
|
||||||
|
if (this.renderer.disposeCompressedElements) {
|
||||||
|
this.renderer.disposeCompressedElements(this.compressibleNodeMapperProvider().map(node) as ITreeNode<ICompressedTreeNode<T>, TFilterData>, index, templateData.templateData, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
disposeTemplate(templateData: IDataTreeListTemplateData<TTemplateData>): void {
|
||||||
this.renderer.disposeTemplate(templateData.templateData);
|
this.renderer.disposeTemplate(templateData.templateData);
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@ let _isMacintosh = false;
|
||||||
let _isLinux = false;
|
let _isLinux = false;
|
||||||
let _isNative = false;
|
let _isNative = false;
|
||||||
let _isWeb = false;
|
let _isWeb = false;
|
||||||
|
let _isIOS = false;
|
||||||
let _locale: string | undefined = undefined;
|
let _locale: string | undefined = undefined;
|
||||||
let _language: string = LANGUAGE_DEFAULT;
|
let _language: string = LANGUAGE_DEFAULT;
|
||||||
let _translationsConfigFile: string | undefined = undefined;
|
let _translationsConfigFile: string | undefined = undefined;
|
||||||
|
@ -41,6 +42,7 @@ declare const global: any;
|
||||||
interface INavigator {
|
interface INavigator {
|
||||||
userAgent: string;
|
userAgent: string;
|
||||||
language: string;
|
language: string;
|
||||||
|
maxTouchPoints?: number;
|
||||||
}
|
}
|
||||||
declare const navigator: INavigator;
|
declare const navigator: INavigator;
|
||||||
declare const self: any;
|
declare const self: any;
|
||||||
|
@ -52,6 +54,7 @@ if (typeof navigator === 'object' && !isElectronRenderer) {
|
||||||
_userAgent = navigator.userAgent;
|
_userAgent = navigator.userAgent;
|
||||||
_isWindows = _userAgent.indexOf('Windows') >= 0;
|
_isWindows = _userAgent.indexOf('Windows') >= 0;
|
||||||
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
|
_isMacintosh = _userAgent.indexOf('Macintosh') >= 0;
|
||||||
|
_isIOS = _userAgent.indexOf('Macintosh') >= 0 && !!navigator.maxTouchPoints && navigator.maxTouchPoints > 0;
|
||||||
_isLinux = _userAgent.indexOf('Linux') >= 0;
|
_isLinux = _userAgent.indexOf('Linux') >= 0;
|
||||||
_isWeb = true;
|
_isWeb = true;
|
||||||
_locale = navigator.language;
|
_locale = navigator.language;
|
||||||
|
@ -106,6 +109,7 @@ export const isMacintosh = _isMacintosh;
|
||||||
export const isLinux = _isLinux;
|
export const isLinux = _isLinux;
|
||||||
export const isNative = _isNative;
|
export const isNative = _isNative;
|
||||||
export const isWeb = _isWeb;
|
export const isWeb = _isWeb;
|
||||||
|
export const isIOS = _isIOS;
|
||||||
export const platform = _platform;
|
export const platform = _platform;
|
||||||
export const userAgent = _userAgent;
|
export const userAgent = _userAgent;
|
||||||
|
|
||||||
|
|
|
@ -33,6 +33,7 @@
|
||||||
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
'xterm-addon-search': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||||
'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
'xterm-addon-web-links': `${window.location.origin}/static/remote/web/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
||||||
'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
|
'semver-umd': `${window.location.origin}/static/remote/web/node_modules/semver-umd/lib/semver-umd.js`,
|
||||||
|
'Msal': `${window.location.origin}/static/node_modules/msal/dist/msal.min.js`,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -37,6 +37,7 @@
|
||||||
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
'xterm-addon-search': `${window.location.origin}/static/node_modules/xterm-addon-search/lib/xterm-addon-search.js`,
|
||||||
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
'xterm-addon-web-links': `${window.location.origin}/static/node_modules/xterm-addon-web-links/lib/xterm-addon-web-links.js`,
|
||||||
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
|
'semver-umd': `${window.location.origin}/static/node_modules/semver-umd/lib/semver-umd.js`,
|
||||||
|
'Msal': `${window.location.origin}/static/node_modules/msal/dist/msal.min.js`,
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -4,6 +4,7 @@
|
||||||
*--------------------------------------------------------------------------------------------*/
|
*--------------------------------------------------------------------------------------------*/
|
||||||
|
|
||||||
import * as dom from 'vs/base/browser/dom';
|
import * as dom from 'vs/base/browser/dom';
|
||||||
|
import * as platform from 'vs/base/common/platform';
|
||||||
import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
|
import { EventType, Gesture, GestureEvent } from 'vs/base/browser/touch';
|
||||||
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler';
|
import { IPointerHandlerHelper, MouseHandler, createMouseMoveEventMerger } from 'vs/editor/browser/controller/mouseHandler';
|
||||||
|
@ -283,7 +284,7 @@ export class PointerHandler extends Disposable {
|
||||||
super();
|
super();
|
||||||
if (window.navigator.msPointerEnabled) {
|
if (window.navigator.msPointerEnabled) {
|
||||||
this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper));
|
this.handler = this._register(new MsPointerHandler(context, viewController, viewHelper));
|
||||||
} else if (((<any>window).PointerEvent && BrowserFeatures.pointerEvents)) {
|
} else if ((platform.isIOS && BrowserFeatures.pointerEvents)) {
|
||||||
this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
|
this.handler = this._register(new PointerEventHandler(context, viewController, viewHelper));
|
||||||
} else if ((<any>window).TouchEvent) {
|
} else if ((<any>window).TouchEvent) {
|
||||||
this.handler = this._register(new TouchHandler(context, viewController, viewHelper));
|
this.handler = this._register(new TouchHandler(context, viewController, viewHelper));
|
||||||
|
|
|
@ -151,7 +151,7 @@ class SaturationBox extends Disposable {
|
||||||
|
|
||||||
this.layout();
|
this.layout();
|
||||||
|
|
||||||
this._register(dom.addDisposableListener(this.domNode, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_DOWN : dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e)));
|
this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e)));
|
||||||
this._register(this.model.onDidChangeColor(this.onDidChangeColor, this));
|
this._register(this.model.onDidChangeColor(this.onDidChangeColor, this));
|
||||||
this.monitor = null;
|
this.monitor = null;
|
||||||
}
|
}
|
||||||
|
@ -166,7 +166,7 @@ class SaturationBox extends Disposable {
|
||||||
|
|
||||||
this.monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangePosition(event.posx - origin.left, event.posy - origin.top), () => null);
|
this.monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangePosition(event.posx - origin.left, event.posy - origin.top), () => null);
|
||||||
|
|
||||||
const mouseUpListener = dom.addDisposableListener(document, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_UP : dom.EventType.MOUSE_UP, () => {
|
const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => {
|
||||||
this._onColorFlushed.fire();
|
this._onColorFlushed.fire();
|
||||||
mouseUpListener.dispose();
|
mouseUpListener.dispose();
|
||||||
if (this.monitor) {
|
if (this.monitor) {
|
||||||
|
@ -251,7 +251,7 @@ abstract class Strip extends Disposable {
|
||||||
this.slider = dom.append(this.domNode, $('.slider'));
|
this.slider = dom.append(this.domNode, $('.slider'));
|
||||||
this.slider.style.top = `0px`;
|
this.slider.style.top = `0px`;
|
||||||
|
|
||||||
this._register(dom.addDisposableListener(this.domNode, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_DOWN : dom.EventType.MOUSE_DOWN, e => this.onMouseDown(e)));
|
this._register(dom.addDisposableGenericMouseDownListner(this.domNode, e => this.onMouseDown(e)));
|
||||||
this.layout();
|
this.layout();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -273,7 +273,7 @@ abstract class Strip extends Disposable {
|
||||||
|
|
||||||
monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangeTop(event.posy - origin.top), () => null);
|
monitor.startMonitoring(standardMouseMoveMerger, event => this.onDidChangeTop(event.posy - origin.top), () => null);
|
||||||
|
|
||||||
const mouseUpListener = dom.addDisposableListener(document, BrowserFeatures.pointerEvents ? dom.EventType.POINTER_UP : dom.EventType.MOUSE_UP, () => {
|
const mouseUpListener = dom.addDisposableGenericMouseUpListner(document, () => {
|
||||||
this._onColorFlushed.fire();
|
this._onColorFlushed.fire();
|
||||||
mouseUpListener.dispose();
|
mouseUpListener.dispose();
|
||||||
monitor.stopMonitoring(true);
|
monitor.stopMonitoring(true);
|
||||||
|
|
|
@ -28,7 +28,7 @@ import { withNullAsUndefined } from 'vs/base/common/types';
|
||||||
|
|
||||||
export interface IResourceLabelProps {
|
export interface IResourceLabelProps {
|
||||||
resource?: URI;
|
resource?: URI;
|
||||||
name?: string;
|
name?: string | string[];
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ export interface IResourceLabelOptions extends IIconLabelValueOptions {
|
||||||
export interface IFileLabelOptions extends IResourceLabelOptions {
|
export interface IFileLabelOptions extends IResourceLabelOptions {
|
||||||
hideLabel?: boolean;
|
hideLabel?: boolean;
|
||||||
hidePath?: boolean;
|
hidePath?: boolean;
|
||||||
|
readonly parentCount?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IResourceLabel extends IDisposable {
|
export interface IResourceLabel extends IDisposable {
|
||||||
|
@ -442,7 +443,8 @@ class ResourceLabelWidget extends IconLabel {
|
||||||
title: '',
|
title: '',
|
||||||
italic: this.options && this.options.italic,
|
italic: this.options && this.options.italic,
|
||||||
matches: this.options && this.options.matches,
|
matches: this.options && this.options.matches,
|
||||||
extraClasses: []
|
extraClasses: [],
|
||||||
|
separator: this.options?.separator
|
||||||
};
|
};
|
||||||
|
|
||||||
const resource = this.label.resource;
|
const resource = this.label.resource;
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { IAction, Action } from 'vs/base/common/actions';
|
||||||
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
import { Separator } from 'vs/base/browser/ui/actionbar/actionbar';
|
||||||
import * as DOM from 'vs/base/browser/dom';
|
import * as DOM from 'vs/base/browser/dom';
|
||||||
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
|
||||||
import { isMacintosh, isWeb } from 'vs/base/common/platform';
|
import { isMacintosh, isWeb, isIOS } from 'vs/base/common/platform';
|
||||||
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
import { IConfigurationService, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
|
@ -674,7 +674,7 @@ export class CustomMenubarControl extends MenubarControl {
|
||||||
}
|
}
|
||||||
|
|
||||||
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => {
|
this._register(DOM.addDisposableListener(window, DOM.EventType.RESIZE, () => {
|
||||||
if (this.menubar && !BrowserFeatures.pointerEvents) {
|
if (this.menubar && !(isIOS && BrowserFeatures.pointerEvents)) {
|
||||||
this.menubar.blur();
|
this.menubar.blur();
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
|
@ -31,7 +31,6 @@ import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService
|
||||||
import { IModeService } from 'vs/editor/common/services/modeService';
|
import { IModeService } from 'vs/editor/common/services/modeService';
|
||||||
import { IModelService } from 'vs/editor/common/services/modelService';
|
import { IModelService } from 'vs/editor/common/services/modelService';
|
||||||
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
import { ICommandService, CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||||
import { IListService, ListWidget } from 'vs/platform/list/browser/listService';
|
|
||||||
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
import { RawContextKey } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { Schemas } from 'vs/base/common/network';
|
import { Schemas } from 'vs/base/common/network';
|
||||||
import { IDialogService, IConfirmationResult, getConfirmMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
import { IDialogService, IConfirmationResult, getConfirmMessage, IFileDialogService } from 'vs/platform/dialogs/common/dialogs';
|
||||||
|
@ -40,7 +39,6 @@ import { IEditorService } from 'vs/workbench/services/editor/common/editorServic
|
||||||
import { Constants } from 'vs/base/common/uint';
|
import { Constants } from 'vs/base/common/uint';
|
||||||
import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
import { CLOSE_EDITORS_AND_GROUP_COMMAND_ID } from 'vs/workbench/browser/parts/editor/editorCommands';
|
||||||
import { coalesce } from 'vs/base/common/arrays';
|
import { coalesce } from 'vs/base/common/arrays';
|
||||||
import { AsyncDataTree } from 'vs/base/browser/ui/tree/asyncDataTree';
|
|
||||||
import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
|
import { ExplorerItem, NewExplorerItem } from 'vs/workbench/contrib/files/common/explorerModel';
|
||||||
import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors';
|
import { onUnexpectedError, getErrorMessage } from 'vs/base/common/errors';
|
||||||
import { asDomUri, triggerDownload } from 'vs/base/browser/dom';
|
import { asDomUri, triggerDownload } from 'vs/base/browser/dom';
|
||||||
|
@ -840,22 +838,6 @@ class ClipboardContentProvider implements ITextModelContentProvider {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IExplorerContext {
|
|
||||||
stat?: ExplorerItem;
|
|
||||||
selection: ExplorerItem[];
|
|
||||||
}
|
|
||||||
|
|
||||||
function getContext(listWidget: ListWidget): IExplorerContext {
|
|
||||||
// These commands can only be triggered when explorer viewlet is visible so get it using the active viewlet
|
|
||||||
const tree = <AsyncDataTree<null, ExplorerItem>>listWidget;
|
|
||||||
const focus = tree.getFocus();
|
|
||||||
const stat = focus.length ? focus[0] : undefined;
|
|
||||||
const selection = tree.getSelection();
|
|
||||||
|
|
||||||
// Only respect the selection if user clicked inside it (focus belongs to it)
|
|
||||||
return { stat, selection: selection && typeof stat !== 'undefined' && selection.indexOf(stat) >= 0 ? selection : [] };
|
|
||||||
}
|
|
||||||
|
|
||||||
function onErrorWithRetry(notificationService: INotificationService, error: any, retry: () => Promise<any>): void {
|
function onErrorWithRetry(notificationService: INotificationService, error: any, retry: () => Promise<any>): void {
|
||||||
notificationService.prompt(Severity.Error, toErrorMessage(error, false),
|
notificationService.prompt(Severity.Error, toErrorMessage(error, false),
|
||||||
[{
|
[{
|
||||||
|
@ -866,7 +848,6 @@ function onErrorWithRetry(notificationService: INotificationService, error: any,
|
||||||
}
|
}
|
||||||
|
|
||||||
async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise<void> {
|
async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boolean): Promise<void> {
|
||||||
const listService = accessor.get(IListService);
|
|
||||||
const explorerService = accessor.get(IExplorerService);
|
const explorerService = accessor.get(IExplorerService);
|
||||||
const fileService = accessor.get(IFileService);
|
const fileService = accessor.get(IFileService);
|
||||||
const textFileService = accessor.get(ITextFileService);
|
const textFileService = accessor.get(ITextFileService);
|
||||||
|
@ -876,47 +857,45 @@ async function openExplorerAndCreate(accessor: ServicesAccessor, isFolder: boole
|
||||||
|
|
||||||
await viewletService.openViewlet(VIEWLET_ID, true);
|
await viewletService.openViewlet(VIEWLET_ID, true);
|
||||||
|
|
||||||
const list = listService.lastFocusedList;
|
const stats = explorerService.getContext(false);
|
||||||
if (list) {
|
const stat = stats.length > 0 ? stats[0] : undefined;
|
||||||
const { stat } = getContext(list);
|
let folder: ExplorerItem;
|
||||||
let folder: ExplorerItem;
|
if (stat) {
|
||||||
if (stat) {
|
folder = stat.isDirectory ? stat : (stat.parent || explorerService.roots[0]);
|
||||||
folder = stat.isDirectory ? stat : (stat.parent || explorerService.roots[0]);
|
} else {
|
||||||
} else {
|
folder = explorerService.roots[0];
|
||||||
folder = explorerService.roots[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (folder.isReadonly) {
|
|
||||||
throw new Error('Parent folder is readonly.');
|
|
||||||
}
|
|
||||||
|
|
||||||
const newStat = new NewExplorerItem(folder, isFolder);
|
|
||||||
await folder.fetchChildren(fileService, explorerService);
|
|
||||||
|
|
||||||
folder.addChild(newStat);
|
|
||||||
|
|
||||||
const onSuccess = (value: string): Promise<void> => {
|
|
||||||
const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value));
|
|
||||||
return createPromise.then(created => {
|
|
||||||
refreshIfSeparator(value, explorerService);
|
|
||||||
return isFolder ? explorerService.select(created.resource, true)
|
|
||||||
: editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined);
|
|
||||||
}, error => {
|
|
||||||
onErrorWithRetry(notificationService, error, () => onSuccess(value));
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
explorerService.setEditable(newStat, {
|
|
||||||
validationMessage: value => validateFileName(newStat, value),
|
|
||||||
onFinish: (value, success) => {
|
|
||||||
folder.removeChild(newStat);
|
|
||||||
explorerService.setEditable(newStat, null);
|
|
||||||
if (success) {
|
|
||||||
onSuccess(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (folder.isReadonly) {
|
||||||
|
throw new Error('Parent folder is readonly.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStat = new NewExplorerItem(folder, isFolder);
|
||||||
|
await folder.fetchChildren(fileService, explorerService);
|
||||||
|
|
||||||
|
folder.addChild(newStat);
|
||||||
|
|
||||||
|
const onSuccess = (value: string): Promise<void> => {
|
||||||
|
const createPromise = isFolder ? fileService.createFolder(resources.joinPath(folder.resource, value)) : textFileService.create(resources.joinPath(folder.resource, value));
|
||||||
|
return createPromise.then(created => {
|
||||||
|
refreshIfSeparator(value, explorerService);
|
||||||
|
return isFolder ? explorerService.select(created.resource, true)
|
||||||
|
: editorService.openEditor({ resource: created.resource, options: { pinned: true } }).then(() => undefined);
|
||||||
|
}, error => {
|
||||||
|
onErrorWithRetry(notificationService, error, () => onSuccess(value));
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
explorerService.setEditable(newStat, {
|
||||||
|
validationMessage: value => validateFileName(newStat, value),
|
||||||
|
onFinish: (value, success) => {
|
||||||
|
folder.removeChild(newStat);
|
||||||
|
explorerService.setEditable(newStat, null);
|
||||||
|
if (success) {
|
||||||
|
onSuccess(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
CommandsRegistry.registerCommand({
|
CommandsRegistry.registerCommand({
|
||||||
|
@ -934,14 +913,11 @@ CommandsRegistry.registerCommand({
|
||||||
});
|
});
|
||||||
|
|
||||||
export const renameHandler = (accessor: ServicesAccessor) => {
|
export const renameHandler = (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
|
||||||
const explorerService = accessor.get(IExplorerService);
|
const explorerService = accessor.get(IExplorerService);
|
||||||
const textFileService = accessor.get(ITextFileService);
|
const textFileService = accessor.get(ITextFileService);
|
||||||
if (!listService.lastFocusedList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { stat } = getContext(listService.lastFocusedList);
|
const stats = explorerService.getContext(false);
|
||||||
|
const stat = stats.length > 0 ? stats[0] : undefined;
|
||||||
if (!stat) {
|
if (!stat) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -962,51 +938,32 @@ export const renameHandler = (accessor: ServicesAccessor) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
export const moveFileToTrashHandler = (accessor: ServicesAccessor) => {
|
export const moveFileToTrashHandler = (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
const explorerService = accessor.get(IExplorerService);
|
||||||
if (!listService.lastFocusedList) {
|
const stats = explorerService.getContext(true);
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
const explorerContext = getContext(listService.lastFocusedList);
|
|
||||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!];
|
|
||||||
|
|
||||||
return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true);
|
return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, true);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const deleteFileHandler = (accessor: ServicesAccessor) => {
|
export const deleteFileHandler = (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
const explorerService = accessor.get(IExplorerService);
|
||||||
if (!listService.lastFocusedList) {
|
const stats = explorerService.getContext(true);
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
const explorerContext = getContext(listService.lastFocusedList);
|
|
||||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat!];
|
|
||||||
|
|
||||||
return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, false);
|
return deleteFiles(accessor.get(ITextFileService), accessor.get(IDialogService), accessor.get(IConfigurationService), accessor.get(IFileService), stats, false);
|
||||||
};
|
};
|
||||||
|
|
||||||
let pasteShouldMove = false;
|
let pasteShouldMove = false;
|
||||||
export const copyFileHandler = (accessor: ServicesAccessor) => {
|
export const copyFileHandler = (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
|
||||||
if (!listService.lastFocusedList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const explorerContext = getContext(listService.lastFocusedList);
|
|
||||||
const explorerService = accessor.get(IExplorerService);
|
const explorerService = accessor.get(IExplorerService);
|
||||||
if (explorerContext.stat) {
|
const stats = explorerService.getContext(true);
|
||||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
|
if (stats.length > 0) {
|
||||||
explorerService.setToCopy(stats, false);
|
explorerService.setToCopy(stats, false);
|
||||||
pasteShouldMove = false;
|
pasteShouldMove = false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const cutFileHandler = (accessor: ServicesAccessor) => {
|
export const cutFileHandler = (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
|
||||||
if (!listService.lastFocusedList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const explorerContext = getContext(listService.lastFocusedList);
|
|
||||||
const explorerService = accessor.get(IExplorerService);
|
const explorerService = accessor.get(IExplorerService);
|
||||||
if (explorerContext.stat) {
|
const stats = explorerService.getContext(true);
|
||||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
|
if (stats.length > 0) {
|
||||||
explorerService.setToCopy(stats, true);
|
explorerService.setToCopy(stats, true);
|
||||||
pasteShouldMove = true;
|
pasteShouldMove = true;
|
||||||
}
|
}
|
||||||
|
@ -1014,47 +971,41 @@ export const cutFileHandler = (accessor: ServicesAccessor) => {
|
||||||
|
|
||||||
export const DOWNLOAD_COMMAND_ID = 'explorer.download';
|
export const DOWNLOAD_COMMAND_ID = 'explorer.download';
|
||||||
const downloadFileHandler = (accessor: ServicesAccessor) => {
|
const downloadFileHandler = (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
|
||||||
if (!listService.lastFocusedList) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const explorerContext = getContext(listService.lastFocusedList);
|
|
||||||
const fileService = accessor.get(IFileService);
|
const fileService = accessor.get(IFileService);
|
||||||
const fileDialogService = accessor.get(IFileDialogService);
|
const fileDialogService = accessor.get(IFileDialogService);
|
||||||
|
const explorerService = accessor.get(IExplorerService);
|
||||||
|
const stats = explorerService.getContext(true);
|
||||||
|
|
||||||
if (explorerContext.stat) {
|
stats.forEach(async s => {
|
||||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
|
if (isWeb) {
|
||||||
stats.forEach(async s => {
|
if (!s.isDirectory) {
|
||||||
if (isWeb) {
|
triggerDownload(asDomUri(s.resource), s.name);
|
||||||
if (!s.isDirectory) {
|
|
||||||
triggerDownload(asDomUri(s.resource), s.name);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
let defaultUri = s.isDirectory ? fileDialogService.defaultFolderPath() : fileDialogService.defaultFilePath();
|
|
||||||
if (defaultUri && !s.isDirectory) {
|
|
||||||
defaultUri = resources.joinPath(defaultUri, s.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
const destination = await fileDialogService.showSaveDialog({
|
|
||||||
availableFileSystems: [Schemas.file],
|
|
||||||
saveLabel: mnemonicButtonLabel(nls.localize('download', "Download")),
|
|
||||||
title: s.isDirectory ? nls.localize('downloadFolder', "Download Folder") : nls.localize('downloadFile', "Download File"),
|
|
||||||
defaultUri
|
|
||||||
});
|
|
||||||
if (destination) {
|
|
||||||
await fileService.copy(s.resource, destination);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
} else {
|
||||||
}
|
let defaultUri = s.isDirectory ? fileDialogService.defaultFolderPath() : fileDialogService.defaultFilePath();
|
||||||
|
if (defaultUri && !s.isDirectory) {
|
||||||
|
defaultUri = resources.joinPath(defaultUri, s.name);
|
||||||
|
}
|
||||||
|
|
||||||
|
const destination = await fileDialogService.showSaveDialog({
|
||||||
|
availableFileSystems: [Schemas.file],
|
||||||
|
saveLabel: mnemonicButtonLabel(nls.localize('download', "Download")),
|
||||||
|
title: s.isDirectory ? nls.localize('downloadFolder', "Download Folder") : nls.localize('downloadFile', "Download File"),
|
||||||
|
defaultUri
|
||||||
|
});
|
||||||
|
if (destination) {
|
||||||
|
await fileService.copy(s.resource, destination);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
CommandsRegistry.registerCommand({
|
CommandsRegistry.registerCommand({
|
||||||
id: DOWNLOAD_COMMAND_ID,
|
id: DOWNLOAD_COMMAND_ID,
|
||||||
handler: downloadFileHandler
|
handler: downloadFileHandler
|
||||||
});
|
});
|
||||||
|
|
||||||
export const pasteFileHandler = async (accessor: ServicesAccessor) => {
|
export const pasteFileHandler = async (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
|
||||||
const clipboardService = accessor.get(IClipboardService);
|
const clipboardService = accessor.get(IClipboardService);
|
||||||
const explorerService = accessor.get(IExplorerService);
|
const explorerService = accessor.get(IExplorerService);
|
||||||
const fileService = accessor.get(IFileService);
|
const fileService = accessor.get(IFileService);
|
||||||
|
@ -1063,72 +1014,65 @@ export const pasteFileHandler = async (accessor: ServicesAccessor) => {
|
||||||
const editorService = accessor.get(IEditorService);
|
const editorService = accessor.get(IEditorService);
|
||||||
const configurationService = accessor.get(IConfigurationService);
|
const configurationService = accessor.get(IConfigurationService);
|
||||||
|
|
||||||
if (listService.lastFocusedList) {
|
const context = explorerService.getContext(true);
|
||||||
const explorerContext = getContext(listService.lastFocusedList);
|
const toPaste = resources.distinctParents(clipboardService.readResources(), r => r);
|
||||||
const toPaste = resources.distinctParents(clipboardService.readResources(), r => r);
|
const element = context.length ? context[0] : explorerService.roots[0];
|
||||||
const element = explorerContext.stat || explorerService.roots[0];
|
|
||||||
|
|
||||||
// Check if target is ancestor of pasted folder
|
// Check if target is ancestor of pasted folder
|
||||||
const stats = await Promise.all(toPaste.map(async fileToPaste => {
|
const stats = await Promise.all(toPaste.map(async fileToPaste => {
|
||||||
|
|
||||||
if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) {
|
if (element.resource.toString() !== fileToPaste.toString() && resources.isEqualOrParent(element.resource, fileToPaste)) {
|
||||||
throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder"));
|
throw new Error(nls.localize('fileIsAncestor', "File to paste is an ancestor of the destination folder"));
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const fileToPasteStat = await fileService.resolve(fileToPaste);
|
|
||||||
|
|
||||||
// Find target
|
|
||||||
let target: ExplorerItem;
|
|
||||||
if (element.resource.toString() === fileToPaste.toString()) {
|
|
||||||
target = element.parent!;
|
|
||||||
} else {
|
|
||||||
target = element.isDirectory ? element : element.parent!;
|
|
||||||
}
|
|
||||||
|
|
||||||
const incrementalNaming = configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;
|
|
||||||
const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming);
|
|
||||||
|
|
||||||
// Move/Copy File
|
|
||||||
if (pasteShouldMove) {
|
|
||||||
return await textFileService.move(fileToPaste, targetFile);
|
|
||||||
} else {
|
|
||||||
return await fileService.copy(fileToPaste, targetFile);
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile. {0}", getErrorMessage(e))));
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}));
|
|
||||||
|
|
||||||
if (pasteShouldMove) {
|
|
||||||
// Cut is done. Make sure to clear cut state.
|
|
||||||
explorerService.setToCopy([], false);
|
|
||||||
}
|
}
|
||||||
if (stats.length >= 1) {
|
|
||||||
const stat = stats[0];
|
try {
|
||||||
if (stat && !stat.isDirectory && stats.length === 1) {
|
const fileToPasteStat = await fileService.resolve(fileToPaste);
|
||||||
await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } });
|
|
||||||
|
// Find target
|
||||||
|
let target: ExplorerItem;
|
||||||
|
if (element.resource.toString() === fileToPaste.toString()) {
|
||||||
|
target = element.parent!;
|
||||||
|
} else {
|
||||||
|
target = element.isDirectory ? element : element.parent!;
|
||||||
}
|
}
|
||||||
if (stat) {
|
|
||||||
await explorerService.select(stat.resource);
|
const incrementalNaming = configurationService.getValue<IFilesConfiguration>().explorer.incrementalNaming;
|
||||||
|
const targetFile = findValidPasteFileTarget(target, { resource: fileToPaste, isDirectory: fileToPasteStat.isDirectory, allowOverwrite: pasteShouldMove }, incrementalNaming);
|
||||||
|
|
||||||
|
// Move/Copy File
|
||||||
|
if (pasteShouldMove) {
|
||||||
|
return await textFileService.move(fileToPaste, targetFile);
|
||||||
|
} else {
|
||||||
|
return await fileService.copy(fileToPaste, targetFile);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
onError(notificationService, new Error(nls.localize('fileDeleted', "File to paste was deleted or moved meanwhile. {0}", getErrorMessage(e))));
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
if (pasteShouldMove) {
|
||||||
|
// Cut is done. Make sure to clear cut state.
|
||||||
|
explorerService.setToCopy([], false);
|
||||||
|
}
|
||||||
|
if (stats.length >= 1) {
|
||||||
|
const stat = stats[0];
|
||||||
|
if (stat && !stat.isDirectory && stats.length === 1) {
|
||||||
|
await editorService.openEditor({ resource: stat.resource, options: { pinned: true, preserveFocus: true } });
|
||||||
|
}
|
||||||
|
if (stat) {
|
||||||
|
await explorerService.select(stat.resource);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) => {
|
export const openFilePreserveFocusHandler = async (accessor: ServicesAccessor) => {
|
||||||
const listService = accessor.get(IListService);
|
|
||||||
const editorService = accessor.get(IEditorService);
|
const editorService = accessor.get(IEditorService);
|
||||||
|
const explorerService = accessor.get(IExplorerService);
|
||||||
|
const stats = explorerService.getContext(true);
|
||||||
|
|
||||||
if (listService.lastFocusedList) {
|
await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({
|
||||||
const explorerContext = getContext(listService.lastFocusedList);
|
resource: s.resource,
|
||||||
if (explorerContext.stat) {
|
options: { preserveFocus: true }
|
||||||
const stats = explorerContext.selection.length > 1 ? explorerContext.selection : [explorerContext.stat];
|
})));
|
||||||
await editorService.openEditors(stats.filter(s => !s.isDirectory).map(s => ({
|
|
||||||
resource: s.resource,
|
|
||||||
options: { preserveFocus: true }
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -11,7 +11,7 @@ import { IHostService } from 'vs/workbench/services/host/browser/host';
|
||||||
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
import { ServicesAccessor, IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
|
||||||
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
import { IViewletService } from 'vs/workbench/services/viewlet/browser/viewlet';
|
||||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||||
import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService } from 'vs/workbench/contrib/files/common/files';
|
import { ExplorerFocusCondition, TextFileContentProvider, VIEWLET_ID, IExplorerService, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext, FilesExplorerFocusCondition } from 'vs/workbench/contrib/files/common/files';
|
||||||
import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet';
|
import { ExplorerViewlet } from 'vs/workbench/contrib/files/browser/explorerViewlet';
|
||||||
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
import { IClipboardService } from 'vs/platform/clipboard/common/clipboardService';
|
||||||
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
|
||||||
|
@ -19,7 +19,7 @@ import { ISaveOptions } from 'vs/workbench/services/workingCopy/common/workingCo
|
||||||
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
import { toErrorMessage } from 'vs/base/common/errorMessage';
|
||||||
import { IListService } from 'vs/platform/list/browser/listService';
|
import { IListService } from 'vs/platform/list/browser/listService';
|
||||||
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
import { CommandsRegistry } from 'vs/platform/commands/common/commands';
|
||||||
import { RawContextKey, IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey';
|
import { RawContextKey, IContextKey, IContextKeyService, ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
|
||||||
import { IResourceInput } from 'vs/platform/editor/common/editor';
|
import { IResourceInput } from 'vs/platform/editor/common/editor';
|
||||||
import { IFileService } from 'vs/platform/files/common/files';
|
import { IFileService } from 'vs/platform/files/common/files';
|
||||||
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
import { IUntitledTextEditorService } from 'vs/workbench/services/untitled/common/untitledTextEditorService';
|
||||||
|
@ -80,6 +80,11 @@ export const ResourceSelectedForCompareContext = new RawContextKey<boolean>('res
|
||||||
export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder';
|
export const REMOVE_ROOT_FOLDER_COMMAND_ID = 'removeRootFolder';
|
||||||
export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace");
|
export const REMOVE_ROOT_FOLDER_LABEL = nls.localize('removeFolderFromWorkspace', "Remove Folder from Workspace");
|
||||||
|
|
||||||
|
export const PREVIOUS_COMPRESSED_FOLDER = 'previousCompressedFolder';
|
||||||
|
export const NEXT_COMPRESSED_FOLDER = 'nextCompressedFolder';
|
||||||
|
export const FIRST_COMPRESSED_FOLDER = 'firstCompressedFolder';
|
||||||
|
export const LAST_COMPRESSED_FOLDER = 'lastCompressedFolder';
|
||||||
|
|
||||||
export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => {
|
export const openWindowCommand = (accessor: ServicesAccessor, toOpen: IWindowOpenable[], options?: IOpenWindowOptions) => {
|
||||||
if (Array.isArray(toOpen)) {
|
if (Array.isArray(toOpen)) {
|
||||||
const hostService = accessor.get(IHostService);
|
const hostService = accessor.get(IHostService);
|
||||||
|
@ -603,3 +608,81 @@ CommandsRegistry.registerCommand({
|
||||||
return workspaceEditingService.removeFolders(resources);
|
return workspaceEditingService.removeFolders(resources);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Compressed item navigation
|
||||||
|
|
||||||
|
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib + 10,
|
||||||
|
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),
|
||||||
|
primary: KeyCode.LeftArrow,
|
||||||
|
id: PREVIOUS_COMPRESSED_FOLDER,
|
||||||
|
handler: (accessor) => {
|
||||||
|
const viewletService = accessor.get(IViewletService);
|
||||||
|
const viewlet = viewletService.getActiveViewlet();
|
||||||
|
|
||||||
|
if (viewlet?.getId() !== VIEWLET_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const explorer = viewlet as ExplorerViewlet;
|
||||||
|
const view = explorer.getExplorerView();
|
||||||
|
view.previousCompressedStat();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib + 10,
|
||||||
|
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),
|
||||||
|
primary: KeyCode.RightArrow,
|
||||||
|
id: NEXT_COMPRESSED_FOLDER,
|
||||||
|
handler: (accessor) => {
|
||||||
|
const viewletService = accessor.get(IViewletService);
|
||||||
|
const viewlet = viewletService.getActiveViewlet();
|
||||||
|
|
||||||
|
if (viewlet?.getId() !== VIEWLET_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const explorer = viewlet as ExplorerViewlet;
|
||||||
|
const view = explorer.getExplorerView();
|
||||||
|
view.nextCompressedStat();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib + 10,
|
||||||
|
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext.negate()),
|
||||||
|
primary: KeyCode.Home,
|
||||||
|
id: FIRST_COMPRESSED_FOLDER,
|
||||||
|
handler: (accessor) => {
|
||||||
|
const viewletService = accessor.get(IViewletService);
|
||||||
|
const viewlet = viewletService.getActiveViewlet();
|
||||||
|
|
||||||
|
if (viewlet?.getId() !== VIEWLET_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const explorer = viewlet as ExplorerViewlet;
|
||||||
|
const view = explorer.getExplorerView();
|
||||||
|
view.firstCompressedStat();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
KeybindingsRegistry.registerCommandAndKeybindingRule({
|
||||||
|
weight: KeybindingWeight.WorkbenchContrib + 10,
|
||||||
|
when: ContextKeyExpr.and(FilesExplorerFocusCondition, ExplorerCompressedFocusContext, ExplorerCompressedLastFocusContext.negate()),
|
||||||
|
primary: KeyCode.End,
|
||||||
|
id: LAST_COMPRESSED_FOLDER,
|
||||||
|
handler: (accessor) => {
|
||||||
|
const viewletService = accessor.get(IViewletService);
|
||||||
|
const viewlet = viewletService.getActiveViewlet();
|
||||||
|
|
||||||
|
if (viewlet?.getId() !== VIEWLET_ID) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const explorer = viewlet as ExplorerViewlet;
|
||||||
|
const view = explorer.getExplorerView();
|
||||||
|
view.lastCompressedStat();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
|
@ -128,6 +128,16 @@
|
||||||
line-height: normal;
|
line-height: normal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name {
|
||||||
|
padding: 1px 1px 1px 2px;
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name:hover > .monaco-highlighted-label,
|
||||||
|
.explorer-viewlet .monaco-list .monaco-list-row.focused .explorer-item .monaco-icon-name-container.multiple > .label-name.active > .monaco-highlighted-label {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
.monaco-workbench.linux .explorer-viewlet .explorer-item .monaco-inputbox,
|
.monaco-workbench.linux .explorer-viewlet .explorer-item .monaco-inputbox,
|
||||||
.monaco-workbench.mac .explorer-viewlet .explorer-item .monaco-inputbox {
|
.monaco-workbench.mac .explorer-viewlet .explorer-item .monaco-inputbox {
|
||||||
height: 22px;
|
height: 22px;
|
||||||
|
|
|
@ -8,7 +8,7 @@ import { URI } from 'vs/base/common/uri';
|
||||||
import * as perf from 'vs/base/common/performance';
|
import * as perf from 'vs/base/common/performance';
|
||||||
import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
|
import { IAction, WorkbenchActionExecutedEvent, WorkbenchActionExecutedClassification } from 'vs/base/common/actions';
|
||||||
import { memoize } from 'vs/base/common/decorators';
|
import { memoize } from 'vs/base/common/decorators';
|
||||||
import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash } from 'vs/workbench/contrib/files/common/files';
|
import { IFilesConfiguration, ExplorerFolderContext, FilesExplorerFocusedContext, ExplorerFocusedContext, ExplorerRootContext, ExplorerResourceReadonlyContext, IExplorerService, ExplorerResourceCut, ExplorerResourceMoveableToTrash, ExplorerCompressedFocusContext, ExplorerCompressedFirstFocusContext, ExplorerCompressedLastFocusContext } from 'vs/workbench/contrib/files/common/files';
|
||||||
import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions';
|
import { NewFolderAction, NewFileAction, FileCopiedContext, RefreshExplorerView, CollapseExplorerView } from 'vs/workbench/contrib/files/browser/fileActions';
|
||||||
import { toResource, SideBySideEditor } from 'vs/workbench/common/editor';
|
import { toResource, SideBySideEditor } from 'vs/workbench/common/editor';
|
||||||
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
import { DiffEditorInput } from 'vs/workbench/common/editor/diffEditorInput';
|
||||||
|
@ -29,7 +29,7 @@ import { DelayedDragHandler } from 'vs/base/browser/dnd';
|
||||||
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
import { IEditorService, SIDE_GROUP, ACTIVE_GROUP } from 'vs/workbench/services/editor/common/editorService';
|
||||||
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
import { IViewletPanelOptions, ViewletPanel } from 'vs/workbench/browser/parts/views/panelViewlet';
|
||||||
import { ILabelService } from 'vs/platform/label/common/label';
|
import { ILabelService } from 'vs/platform/label/common/label';
|
||||||
import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate } from 'vs/workbench/contrib/files/browser/views/explorerViewer';
|
import { ExplorerDelegate, ExplorerAccessibilityProvider, ExplorerDataSource, FilesRenderer, ICompressedNavigationController, FilesFilter, FileSorter, FileDragAndDrop, ExplorerCompressionDelegate } from 'vs/workbench/contrib/files/browser/views/explorerViewer';
|
||||||
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
import { IThemeService } from 'vs/platform/theme/common/themeService';
|
||||||
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
import { IWorkbenchThemeService } from 'vs/workbench/services/themes/common/workbenchThemeService';
|
||||||
import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
|
import { ITreeContextMenuEvent } from 'vs/base/browser/ui/tree/tree';
|
||||||
|
@ -49,8 +49,19 @@ import { values } from 'vs/base/common/map';
|
||||||
import { first } from 'vs/base/common/arrays';
|
import { first } from 'vs/base/common/arrays';
|
||||||
import { withNullAsUndefined } from 'vs/base/common/types';
|
import { withNullAsUndefined } from 'vs/base/common/types';
|
||||||
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
import { IFileService, FileSystemProviderCapabilities } from 'vs/platform/files/common/files';
|
||||||
import { dispose } from 'vs/base/common/lifecycle';
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { Event } from 'vs/base/common/event';
|
import { Event } from 'vs/base/common/event';
|
||||||
|
import { attachStyler, IColorMapping } from 'vs/platform/theme/common/styler';
|
||||||
|
import { ColorValue, listDropBackground } from 'vs/platform/theme/common/colorRegistry';
|
||||||
|
import { Color } from 'vs/base/common/color';
|
||||||
|
|
||||||
|
interface IExplorerViewColors extends IColorMapping {
|
||||||
|
listDropBackground?: ColorValue | undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IExplorerViewStyles {
|
||||||
|
listDropBackground?: Color;
|
||||||
|
}
|
||||||
|
|
||||||
export class ExplorerView extends ViewletPanel {
|
export class ExplorerView extends ViewletPanel {
|
||||||
static readonly ID: string = 'workbench.explorer.fileView';
|
static readonly ID: string = 'workbench.explorer.fileView';
|
||||||
|
@ -65,6 +76,14 @@ export class ExplorerView extends ViewletPanel {
|
||||||
private rootContext: IContextKey<boolean>;
|
private rootContext: IContextKey<boolean>;
|
||||||
private resourceMoveableToTrash: IContextKey<boolean>;
|
private resourceMoveableToTrash: IContextKey<boolean>;
|
||||||
|
|
||||||
|
private renderer!: FilesRenderer;
|
||||||
|
|
||||||
|
private styleElement!: HTMLStyleElement;
|
||||||
|
private compressedFocusContext: IContextKey<boolean>;
|
||||||
|
private compressedFocusFirstContext: IContextKey<boolean>;
|
||||||
|
private compressedFocusLastContext: IContextKey<boolean>;
|
||||||
|
private compressedNavigationController: ICompressedNavigationController | undefined;
|
||||||
|
|
||||||
// Refresh is needed on the initial explorer open
|
// Refresh is needed on the initial explorer open
|
||||||
private shouldRefresh = true;
|
private shouldRefresh = true;
|
||||||
private dragHandler!: DelayedDragHandler;
|
private dragHandler!: DelayedDragHandler;
|
||||||
|
@ -96,15 +115,20 @@ export class ExplorerView extends ViewletPanel {
|
||||||
|
|
||||||
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
|
this.resourceContext = instantiationService.createInstance(ResourceContextKey);
|
||||||
this._register(this.resourceContext);
|
this._register(this.resourceContext);
|
||||||
|
|
||||||
this.folderContext = ExplorerFolderContext.bindTo(contextKeyService);
|
this.folderContext = ExplorerFolderContext.bindTo(contextKeyService);
|
||||||
this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService);
|
this.readonlyContext = ExplorerResourceReadonlyContext.bindTo(contextKeyService);
|
||||||
this.rootContext = ExplorerRootContext.bindTo(contextKeyService);
|
this.rootContext = ExplorerRootContext.bindTo(contextKeyService);
|
||||||
this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService);
|
this.resourceMoveableToTrash = ExplorerResourceMoveableToTrash.bindTo(contextKeyService);
|
||||||
|
this.compressedFocusContext = ExplorerCompressedFocusContext.bindTo(contextKeyService);
|
||||||
|
this.compressedFocusFirstContext = ExplorerCompressedFirstFocusContext.bindTo(contextKeyService);
|
||||||
|
this.compressedFocusLastContext = ExplorerCompressedLastFocusContext.bindTo(contextKeyService);
|
||||||
|
|
||||||
|
this.explorerService.registerContextProvider(this);
|
||||||
|
|
||||||
const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService);
|
const decorationProvider = new ExplorerDecorationsProvider(this.explorerService, contextService);
|
||||||
this._register(decorationService.registerDecorationsProvider(decorationProvider));
|
this._register(decorationService.registerDecorationsProvider(decorationProvider));
|
||||||
this._register(decorationProvider);
|
this._register(decorationProvider);
|
||||||
this._register(this.resourceContext);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get name(): string {
|
get name(): string {
|
||||||
|
@ -161,6 +185,10 @@ export class ExplorerView extends ViewletPanel {
|
||||||
|
|
||||||
renderBody(container: HTMLElement): void {
|
renderBody(container: HTMLElement): void {
|
||||||
const treeContainer = DOM.append(container, DOM.$('.explorer-folders-view'));
|
const treeContainer = DOM.append(container, DOM.$('.explorer-folders-view'));
|
||||||
|
|
||||||
|
this.styleElement = DOM.createStyleSheet(treeContainer);
|
||||||
|
attachStyler<IExplorerViewColors>(this.themeService, { listDropBackground }, this.styleListDropBackground.bind(this));
|
||||||
|
|
||||||
this.createTree(treeContainer);
|
this.createTree(treeContainer);
|
||||||
|
|
||||||
if (this.toolbar) {
|
if (this.toolbar) {
|
||||||
|
@ -254,6 +282,39 @@ export class ExplorerView extends ViewletPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getContext(respectMultiSelection: boolean): ExplorerItem[] {
|
||||||
|
let focusedStat: ExplorerItem | undefined;
|
||||||
|
|
||||||
|
if (this.compressedNavigationController) {
|
||||||
|
focusedStat = this.compressedNavigationController.current;
|
||||||
|
} else {
|
||||||
|
const focus = this.tree.getFocus();
|
||||||
|
focusedStat = focus.length ? focus[0] : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!focusedStat) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedStats: ExplorerItem[] = [];
|
||||||
|
|
||||||
|
for (const stat of this.tree.getSelection()) {
|
||||||
|
const controller = this.renderer.getCompressedNavigationController(stat);
|
||||||
|
|
||||||
|
if (controller) {
|
||||||
|
selectedStats.push(...controller.items);
|
||||||
|
} else {
|
||||||
|
selectedStats.push(stat);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (respectMultiSelection && selectedStats.indexOf(focusedStat) >= 0) {
|
||||||
|
return selectedStats;
|
||||||
|
}
|
||||||
|
|
||||||
|
return [focusedStat];
|
||||||
|
}
|
||||||
|
|
||||||
private selectActiveFile(deselect?: boolean, reveal = this.autoReveal): void {
|
private selectActiveFile(deselect?: boolean, reveal = this.autoReveal): void {
|
||||||
if (this.autoReveal) {
|
if (this.autoReveal) {
|
||||||
const activeFile = this.getActiveFile();
|
const activeFile = this.getActiveFile();
|
||||||
|
@ -278,14 +339,14 @@ export class ExplorerView extends ViewletPanel {
|
||||||
this._register(explorerLabels);
|
this._register(explorerLabels);
|
||||||
|
|
||||||
const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat);
|
const updateWidth = (stat: ExplorerItem) => this.tree.updateWidth(stat);
|
||||||
const filesRenderer = this.instantiationService.createInstance(FilesRenderer, explorerLabels, updateWidth);
|
this.renderer = this.instantiationService.createInstance(FilesRenderer, explorerLabels, updateWidth);
|
||||||
this._register(filesRenderer);
|
this._register(this.renderer);
|
||||||
|
|
||||||
this._register(createFileIconThemableTreeContainerScope(container, this.themeService));
|
this._register(createFileIconThemableTreeContainerScope(container, this.themeService));
|
||||||
|
|
||||||
const isCompressionEnabled = () => this.configurationService.getValue<boolean>('explorer.compressSingleChildFolders');
|
const isCompressionEnabled = () => this.configurationService.getValue<boolean>('explorer.compressSingleChildFolders');
|
||||||
|
|
||||||
this.tree = this.instantiationService.createInstance<typeof WorkbenchCompressibleAsyncDataTree, WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>>(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [filesRenderer],
|
this.tree = this.instantiationService.createInstance<typeof WorkbenchCompressibleAsyncDataTree, WorkbenchCompressibleAsyncDataTree<ExplorerItem | ExplorerItem[], ExplorerItem, FuzzyScore>>(WorkbenchCompressibleAsyncDataTree, 'FileExplorer', container, new ExplorerDelegate(), new ExplorerCompressionDelegate(), [this.renderer],
|
||||||
this.instantiationService.createInstance(ExplorerDataSource), {
|
this.instantiationService.createInstance(ExplorerDataSource), {
|
||||||
compressionEnabled: isCompressionEnabled(),
|
compressionEnabled: isCompressionEnabled(),
|
||||||
accessibilityProvider: new ExplorerAccessibilityProvider(),
|
accessibilityProvider: new ExplorerAccessibilityProvider(),
|
||||||
|
@ -391,7 +452,7 @@ export class ExplorerView extends ViewletPanel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private setContextKeys(stat: ExplorerItem | null): void {
|
private setContextKeys(stat: ExplorerItem | null | undefined): void {
|
||||||
const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER;
|
const isSingleFolder = this.contextService.getWorkbenchState() === WorkbenchState.FOLDER;
|
||||||
const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null;
|
const resource = stat ? stat.resource : isSingleFolder ? this.contextService.getWorkspace().folders[0].uri : null;
|
||||||
this.resourceContext.set(resource);
|
this.resourceContext.set(resource);
|
||||||
|
@ -401,7 +462,18 @@ export class ExplorerView extends ViewletPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onContextMenu(e: ITreeContextMenuEvent<ExplorerItem>): void {
|
private onContextMenu(e: ITreeContextMenuEvent<ExplorerItem>): void {
|
||||||
const stat = e.element;
|
const disposables = new DisposableStore();
|
||||||
|
let stat = e.element;
|
||||||
|
let anchor = e.anchor;
|
||||||
|
|
||||||
|
// Compressed folders
|
||||||
|
if (stat) {
|
||||||
|
const controller = this.renderer.getCompressedNavigationController(stat);
|
||||||
|
|
||||||
|
if (controller) {
|
||||||
|
anchor = controller.labels[controller.index];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// update dynamic contexts
|
// update dynamic contexts
|
||||||
this.fileCopiedContextKey.set(this.clipboardService.hasResources());
|
this.fileCopiedContextKey.set(this.clipboardService.hasResources());
|
||||||
|
@ -412,17 +484,17 @@ export class ExplorerView extends ViewletPanel {
|
||||||
const actions: IAction[] = [];
|
const actions: IAction[] = [];
|
||||||
const roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object.
|
const roots = this.explorerService.roots; // If the click is outside of the elements pass the root resource if there is only one root. If there are multiple roots pass empty object.
|
||||||
const arg = stat instanceof ExplorerItem ? stat.resource : roots.length === 1 ? roots[0].resource : {};
|
const arg = stat instanceof ExplorerItem ? stat.resource : roots.length === 1 ? roots[0].resource : {};
|
||||||
const actionsDisposable = createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService);
|
disposables.add(createAndFillInContextMenuActions(this.contributedContextMenu, { arg, shouldForwardArgs: true }, actions, this.contextMenuService));
|
||||||
|
|
||||||
this.contextMenuService.showContextMenu({
|
this.contextMenuService.showContextMenu({
|
||||||
getAnchor: () => e.anchor,
|
getAnchor: () => anchor,
|
||||||
getActions: () => actions,
|
getActions: () => actions,
|
||||||
onHide: (wasCancelled?: boolean) => {
|
onHide: (wasCancelled?: boolean) => {
|
||||||
if (wasCancelled) {
|
if (wasCancelled) {
|
||||||
this.tree.domFocus();
|
this.tree.domFocus();
|
||||||
}
|
}
|
||||||
|
|
||||||
dispose(actionsDisposable);
|
disposables.dispose();
|
||||||
},
|
},
|
||||||
getActionsContext: () => stat && selection && selection.indexOf(stat) >= 0
|
getActionsContext: () => stat && selection && selection.indexOf(stat) >= 0
|
||||||
? selection.map((fs: ExplorerItem) => fs.resource)
|
? selection.map((fs: ExplorerItem) => fs.resource)
|
||||||
|
@ -431,7 +503,7 @@ export class ExplorerView extends ViewletPanel {
|
||||||
}
|
}
|
||||||
|
|
||||||
private onFocusChanged(elements: ExplorerItem[]): void {
|
private onFocusChanged(elements: ExplorerItem[]): void {
|
||||||
const stat = elements && elements.length ? elements[0] : null;
|
const stat = elements && elements.length ? elements[0] : undefined;
|
||||||
this.setContextKeys(stat);
|
this.setContextKeys(stat);
|
||||||
|
|
||||||
if (stat) {
|
if (stat) {
|
||||||
|
@ -441,6 +513,17 @@ export class ExplorerView extends ViewletPanel {
|
||||||
} else {
|
} else {
|
||||||
this.resourceMoveableToTrash.reset();
|
this.resourceMoveableToTrash.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.compressedNavigationController = stat && this.renderer.getCompressedNavigationController(stat);
|
||||||
|
|
||||||
|
if (!this.compressedNavigationController) {
|
||||||
|
this.compressedFocusContext.set(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compressedFocusContext.set(true);
|
||||||
|
// this.compressedNavigationController.last();
|
||||||
|
this.updateCompressedNavigationContextKeys(this.compressedNavigationController);
|
||||||
}
|
}
|
||||||
|
|
||||||
// General methods
|
// General methods
|
||||||
|
@ -589,6 +672,60 @@ export class ExplorerView extends ViewletPanel {
|
||||||
this.tree.collapseAll();
|
this.tree.collapseAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
previousCompressedStat(): void {
|
||||||
|
if (!this.compressedNavigationController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compressedNavigationController.previous();
|
||||||
|
this.updateCompressedNavigationContextKeys(this.compressedNavigationController);
|
||||||
|
}
|
||||||
|
|
||||||
|
nextCompressedStat(): void {
|
||||||
|
if (!this.compressedNavigationController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compressedNavigationController.next();
|
||||||
|
this.updateCompressedNavigationContextKeys(this.compressedNavigationController);
|
||||||
|
}
|
||||||
|
|
||||||
|
firstCompressedStat(): void {
|
||||||
|
if (!this.compressedNavigationController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compressedNavigationController.first();
|
||||||
|
this.updateCompressedNavigationContextKeys(this.compressedNavigationController);
|
||||||
|
}
|
||||||
|
|
||||||
|
lastCompressedStat(): void {
|
||||||
|
if (!this.compressedNavigationController) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compressedNavigationController.last();
|
||||||
|
this.updateCompressedNavigationContextKeys(this.compressedNavigationController);
|
||||||
|
}
|
||||||
|
|
||||||
|
private updateCompressedNavigationContextKeys(controller: ICompressedNavigationController): void {
|
||||||
|
this.compressedFocusFirstContext.set(controller.index === 0);
|
||||||
|
this.compressedFocusLastContext.set(controller.index === controller.count - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
styleListDropBackground(styles: IExplorerViewStyles): void {
|
||||||
|
const content: string[] = [];
|
||||||
|
|
||||||
|
if (styles.listDropBackground) {
|
||||||
|
content.push(`.explorer-viewlet .explorer-item .monaco-icon-name-container.multiple > .label-name.drop-target { background-color: ${styles.listDropBackground}; }`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newStyles = content.join('\n');
|
||||||
|
if (newStyles !== this.styleElement.innerHTML) {
|
||||||
|
this.styleElement.innerHTML = newStyles;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
if (this.dragHandler) {
|
if (this.dragHandler) {
|
||||||
this.dragHandler.dispose();
|
this.dragHandler.dispose();
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { INotificationService, Severity } from 'vs/platform/notification/common/
|
||||||
import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
import { IFileService, FileKind, FileOperationError, FileOperationResult } from 'vs/platform/files/common/files';
|
||||||
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
import { IWorkbenchLayoutService } from 'vs/workbench/services/layout/browser/layoutService';
|
||||||
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
|
||||||
import { IDisposable, Disposable, dispose, toDisposable } from 'vs/base/common/lifecycle';
|
import { IDisposable, Disposable, dispose, toDisposable, DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { KeyCode } from 'vs/base/common/keyCodes';
|
import { KeyCode } from 'vs/base/common/keyCodes';
|
||||||
import { IFileLabelOptions, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
|
import { IFileLabelOptions, IResourceLabel, ResourceLabels } from 'vs/workbench/browser/labels';
|
||||||
import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
|
import { ITreeNode, ITreeFilter, TreeVisibility, TreeFilterResult, IAsyncDataSource, ITreeSorter, ITreeDragAndDrop, ITreeDragOverReaction, TreeDragOverBubble } from 'vs/base/browser/ui/tree/tree';
|
||||||
|
@ -51,6 +51,9 @@ import { ITreeCompressionDelegate } from 'vs/base/browser/ui/tree/asyncDataTree'
|
||||||
import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
|
import { ICompressibleTreeRenderer } from 'vs/base/browser/ui/tree/objectTree';
|
||||||
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
import { ICompressedTreeNode } from 'vs/base/browser/ui/tree/compressedObjectTreeModel';
|
||||||
import { VSBuffer } from 'vs/base/common/buffer';
|
import { VSBuffer } from 'vs/base/common/buffer';
|
||||||
|
import { ILabelService } from 'vs/platform/label/common/label';
|
||||||
|
import { isNumber } from 'vs/base/common/types';
|
||||||
|
import { domEvent } from 'vs/base/browser/event';
|
||||||
|
|
||||||
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
|
export class ExplorerDelegate implements IListVirtualDelegate<ExplorerItem> {
|
||||||
|
|
||||||
|
@ -114,6 +117,77 @@ export class ExplorerDataSource implements IAsyncDataSource<ExplorerItem | Explo
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ICompressedNavigationController {
|
||||||
|
readonly current: ExplorerItem;
|
||||||
|
readonly items: ExplorerItem[];
|
||||||
|
readonly labels: HTMLElement[];
|
||||||
|
readonly index: number;
|
||||||
|
readonly count: number;
|
||||||
|
previous(): void;
|
||||||
|
next(): void;
|
||||||
|
first(): void;
|
||||||
|
last(): void;
|
||||||
|
setIndex(index: number): void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CompressedNavigationController implements ICompressedNavigationController {
|
||||||
|
|
||||||
|
private _index: number;
|
||||||
|
readonly labels: HTMLElement[];
|
||||||
|
|
||||||
|
get index(): number { return this._index; }
|
||||||
|
get count(): number { return this.items.length; }
|
||||||
|
get current(): ExplorerItem { return this.items[this._index]!; }
|
||||||
|
|
||||||
|
constructor(readonly items: ExplorerItem[], templateData: IFileTemplateData) {
|
||||||
|
this._index = items.length - 1;
|
||||||
|
this.labels = Array.from(templateData.container.querySelectorAll('.label-name')) as HTMLElement[];
|
||||||
|
DOM.addClass(this.labels[this._index], 'active');
|
||||||
|
}
|
||||||
|
|
||||||
|
previous(): void {
|
||||||
|
if (this._index <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setIndex(this._index - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
next(): void {
|
||||||
|
if (this._index >= this.items.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setIndex(this._index + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
first(): void {
|
||||||
|
if (this._index === 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setIndex(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
last(): void {
|
||||||
|
if (this._index === this.items.length - 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setIndex(this.items.length - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
setIndex(index: number): void {
|
||||||
|
if (index < 0 || index >= this.items.length) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
DOM.removeClass(this.labels[this._index], 'active');
|
||||||
|
this._index = index;
|
||||||
|
DOM.addClass(this.labels[this._index], 'active');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface IFileTemplateData {
|
export interface IFileTemplateData {
|
||||||
elementDisposable: IDisposable;
|
elementDisposable: IDisposable;
|
||||||
label: IResourceLabel;
|
label: IResourceLabel;
|
||||||
|
@ -125,6 +199,7 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
|
|
||||||
private config: IFilesConfiguration;
|
private config: IFilesConfiguration;
|
||||||
private configListener: IDisposable;
|
private configListener: IDisposable;
|
||||||
|
private compressedNavigationControllers = new Map<ExplorerItem, CompressedNavigationController>();
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private labels: ResourceLabels,
|
private labels: ResourceLabels,
|
||||||
|
@ -132,7 +207,8 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
@IContextViewService private readonly contextViewService: IContextViewService,
|
@IContextViewService private readonly contextViewService: IContextViewService,
|
||||||
@IThemeService private readonly themeService: IThemeService,
|
@IThemeService private readonly themeService: IThemeService,
|
||||||
@IConfigurationService private readonly configurationService: IConfigurationService,
|
@IConfigurationService private readonly configurationService: IConfigurationService,
|
||||||
@IExplorerService private readonly explorerService: IExplorerService
|
@IExplorerService private readonly explorerService: IExplorerService,
|
||||||
|
@ILabelService private readonly labelService: ILabelService
|
||||||
) {
|
) {
|
||||||
this.config = this.configurationService.getValue<IFilesConfiguration>();
|
this.config = this.configurationService.getValue<IFilesConfiguration>();
|
||||||
this.configListener = this.configurationService.onDidChangeConfiguration(e => {
|
this.configListener = this.configurationService.onDidChangeConfiguration(e => {
|
||||||
|
@ -158,6 +234,8 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
const stat = node.element;
|
const stat = node.element;
|
||||||
const editableData = this.explorerService.getEditableData(stat);
|
const editableData = this.explorerService.getEditableData(stat);
|
||||||
|
|
||||||
|
DOM.removeClass(templateData.label.element, 'compressed');
|
||||||
|
|
||||||
// File Label
|
// File Label
|
||||||
if (!editableData) {
|
if (!editableData) {
|
||||||
templateData.label.element.style.display = 'flex';
|
templateData.label.element.style.display = 'flex';
|
||||||
|
@ -175,23 +253,44 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
templateData.elementDisposable.dispose();
|
templateData.elementDisposable.dispose();
|
||||||
|
|
||||||
const stat = node.element.elements[node.element.elements.length - 1];
|
const stat = node.element.elements[node.element.elements.length - 1];
|
||||||
const label = node.element.elements.map(e => e.name).join('/');
|
const label = node.element.elements.map(e => e.name);
|
||||||
const editableData = this.explorerService.getEditableData(stat);
|
const editableData = this.explorerService.getEditableData(stat);
|
||||||
|
|
||||||
// File Label
|
// File Label
|
||||||
if (!editableData) {
|
if (!editableData) {
|
||||||
|
DOM.addClass(templateData.label.element, 'compressed');
|
||||||
templateData.label.element.style.display = 'flex';
|
templateData.label.element.style.display = 'flex';
|
||||||
templateData.elementDisposable = this.renderStat(stat, label, node.filterData, templateData);
|
|
||||||
|
const disposables = new DisposableStore();
|
||||||
|
disposables.add(this.renderStat(stat, label, node.filterData, templateData));
|
||||||
|
|
||||||
|
const compressedNavigationController = new CompressedNavigationController(node.element.elements, templateData);
|
||||||
|
this.compressedNavigationControllers.set(stat, compressedNavigationController);
|
||||||
|
|
||||||
|
domEvent(templateData.container, 'mousedown')(e => {
|
||||||
|
const result = getIconLabelNameFromHTMLElement(e.target);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
compressedNavigationController.setIndex(result.index);
|
||||||
|
}
|
||||||
|
}, undefined, disposables);
|
||||||
|
|
||||||
|
disposables.add(toDisposable(() => {
|
||||||
|
this.compressedNavigationControllers.delete(stat);
|
||||||
|
}));
|
||||||
|
|
||||||
|
templateData.elementDisposable = disposables;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Input Box
|
// Input Box
|
||||||
else {
|
else {
|
||||||
|
DOM.removeClass(templateData.label.element, 'compressed');
|
||||||
templateData.label.element.style.display = 'none';
|
templateData.label.element.style.display = 'none';
|
||||||
templateData.elementDisposable = this.renderInputBox(templateData.container, stat, editableData);
|
templateData.elementDisposable = this.renderInputBox(templateData.container, stat, editableData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private renderStat(stat: ExplorerItem, label: string, filterData: FuzzyScore | undefined, templateData: IFileTemplateData): IDisposable {
|
private renderStat(stat: ExplorerItem, label: string | string[], filterData: FuzzyScore | undefined, templateData: IFileTemplateData): IDisposable {
|
||||||
templateData.label.element.style.display = 'flex';
|
templateData.label.element.style.display = 'flex';
|
||||||
const extraClasses = ['explorer-item'];
|
const extraClasses = ['explorer-item'];
|
||||||
if (this.explorerService.isCut(stat)) {
|
if (this.explorerService.isCut(stat)) {
|
||||||
|
@ -202,7 +301,8 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE,
|
fileKind: stat.isRoot ? FileKind.ROOT_FOLDER : stat.isDirectory ? FileKind.FOLDER : FileKind.FILE,
|
||||||
extraClasses,
|
extraClasses,
|
||||||
fileDecorations: this.config.explorer.decorations,
|
fileDecorations: this.config.explorer.decorations,
|
||||||
matches: createMatches(filterData)
|
matches: createMatches(filterData),
|
||||||
|
separator: this.labelService.getSeparator(stat.resource.scheme, stat.resource.authority)
|
||||||
});
|
});
|
||||||
|
|
||||||
return templateData.label.onDidRender(() => {
|
return templateData.label.onDidRender(() => {
|
||||||
|
@ -227,6 +327,9 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
|
|
||||||
label.setFile(joinPath(parent, value || ' '), labelOptions); // Use icon for ' ' if name is empty.
|
label.setFile(joinPath(parent, value || ' '), labelOptions); // Use icon for ' ' if name is empty.
|
||||||
|
|
||||||
|
// hack: hide label
|
||||||
|
(label.element.firstElementChild as HTMLElement).style.display = 'none';
|
||||||
|
|
||||||
// Input field for name
|
// Input field for name
|
||||||
const inputBox = new InputBox(label.element, this.contextViewService, {
|
const inputBox = new InputBox(label.element, this.contextViewService, {
|
||||||
validationOptions: {
|
validationOptions: {
|
||||||
|
@ -290,7 +393,11 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
disposeElement?(element: ITreeNode<ExplorerItem, FuzzyScore>, index: number, templateData: IFileTemplateData): void {
|
disposeElement(element: ITreeNode<ExplorerItem, FuzzyScore>, index: number, templateData: IFileTemplateData): void {
|
||||||
|
templateData.elementDisposable.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
disposeCompressedElements(node: ITreeNode<ICompressedTreeNode<ExplorerItem>, FuzzyScore>, index: number, templateData: IFileTemplateData): void {
|
||||||
templateData.elementDisposable.dispose();
|
templateData.elementDisposable.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,6 +406,10 @@ export class FilesRenderer implements ICompressibleTreeRenderer<ExplorerItem, Fu
|
||||||
templateData.label.dispose();
|
templateData.label.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getCompressedNavigationController(stat: ExplorerItem): ICompressedNavigationController | undefined {
|
||||||
|
return this.compressedNavigationControllers.get(stat);
|
||||||
|
}
|
||||||
|
|
||||||
dispose(): void {
|
dispose(): void {
|
||||||
this.configListener.dispose();
|
this.configListener.dispose();
|
||||||
}
|
}
|
||||||
|
@ -468,6 +579,9 @@ const fileOverwriteConfirm = (name: string) => {
|
||||||
export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
private static readonly CONFIRM_DND_SETTING_KEY = 'explorer.confirmDragAndDrop';
|
private static readonly CONFIRM_DND_SETTING_KEY = 'explorer.confirmDragAndDrop';
|
||||||
|
|
||||||
|
private compressedDragOverElement: HTMLElement | undefined;
|
||||||
|
private compressedDropTargetDisposable: IDisposable = Disposable.None;
|
||||||
|
|
||||||
private toDispose: IDisposable[];
|
private toDispose: IDisposable[];
|
||||||
private dropEnabled = false;
|
private dropEnabled = false;
|
||||||
|
|
||||||
|
@ -498,6 +612,42 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compressed folders
|
||||||
|
if (target) {
|
||||||
|
const compressedTarget = FileDragAndDrop.getCompressedStatFromDragEvent(target, originalEvent);
|
||||||
|
|
||||||
|
if (compressedTarget) {
|
||||||
|
const iconLabelName = getIconLabelNameFromHTMLElement(originalEvent.target);
|
||||||
|
|
||||||
|
if (iconLabelName && iconLabelName.index < iconLabelName.count - 1) {
|
||||||
|
const result = this._onDragOver(data, compressedTarget, targetIndex, originalEvent);
|
||||||
|
|
||||||
|
if (result) {
|
||||||
|
if (iconLabelName.element !== this.compressedDragOverElement) {
|
||||||
|
this.compressedDragOverElement = iconLabelName.element;
|
||||||
|
this.compressedDropTargetDisposable.dispose();
|
||||||
|
this.compressedDropTargetDisposable = toDisposable(() => {
|
||||||
|
DOM.removeClass(iconLabelName.element, 'drop-target');
|
||||||
|
this.compressedDragOverElement = undefined;
|
||||||
|
});
|
||||||
|
|
||||||
|
DOM.addClass(iconLabelName.element, 'drop-target');
|
||||||
|
}
|
||||||
|
|
||||||
|
return typeof result === 'boolean' ? result : { ...result, feedback: [] };
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compressedDropTargetDisposable.dispose();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.compressedDropTargetDisposable.dispose();
|
||||||
|
return this._onDragOver(data, target, targetIndex, originalEvent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private _onDragOver(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): boolean | ITreeDragOverReaction {
|
||||||
const isCopy = originalEvent && ((originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh));
|
const isCopy = originalEvent && ((originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh));
|
||||||
const fromDesktop = data instanceof DesktopDragAndDropData;
|
const fromDesktop = data instanceof DesktopDragAndDropData;
|
||||||
const effect = (fromDesktop || isCopy) ? ListDragOverEffect.Copy : ListDragOverEffect.Move;
|
const effect = (fromDesktop || isCopy) ? ListDragOverEffect.Copy : ListDragOverEffect.Move;
|
||||||
|
@ -516,7 +666,7 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
|
|
||||||
// In-Explorer DND
|
// In-Explorer DND
|
||||||
else {
|
else {
|
||||||
const items = (data as ElementsDragAndDropData<ExplorerItem>).elements;
|
const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData<ExplorerItem, ExplorerItem[]>);
|
||||||
|
|
||||||
if (!target) {
|
if (!target) {
|
||||||
// Dropping onto the empty area. Do not accept if items dragged are already
|
// Dropping onto the empty area. Do not accept if items dragged are already
|
||||||
|
@ -591,16 +741,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
return element.resource.toString();
|
return element.resource.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
getDragLabel(elements: ExplorerItem[]): string | undefined {
|
getDragLabel(elements: ExplorerItem[], originalEvent: DragEvent): string | undefined {
|
||||||
if (elements.length > 1) {
|
if (elements.length === 1) {
|
||||||
return String(elements.length);
|
const stat = FileDragAndDrop.getCompressedStatFromDragEvent(elements[0], originalEvent);
|
||||||
|
return stat.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
return elements[0].name;
|
return String(elements.length);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
onDragStart(data: IDragAndDropData, originalEvent: DragEvent): void {
|
||||||
const items = (data as ElementsDragAndDropData<ExplorerItem>).elements;
|
const items = FileDragAndDrop.getStatsFromDragAndDropData(data as ElementsDragAndDropData<ExplorerItem, ExplorerItem[]>, originalEvent);
|
||||||
if (items && items.length && originalEvent.dataTransfer) {
|
if (items && items.length && originalEvent.dataTransfer) {
|
||||||
// Apply some datatransfer types to allow for dragging the element outside of the application
|
// Apply some datatransfer types to allow for dragging the element outside of the application
|
||||||
this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent);
|
this.instantiationService.invokeFunction(fillResourceDataTransfers, items, originalEvent);
|
||||||
|
@ -615,6 +766,17 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
}
|
}
|
||||||
|
|
||||||
drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
|
drop(data: IDragAndDropData, target: ExplorerItem | undefined, targetIndex: number | undefined, originalEvent: DragEvent): void {
|
||||||
|
this.compressedDropTargetDisposable.dispose();
|
||||||
|
|
||||||
|
// Find compressed target
|
||||||
|
if (target) {
|
||||||
|
const compressedTarget = FileDragAndDrop.getCompressedStatFromDragEvent(target, originalEvent);
|
||||||
|
|
||||||
|
if (compressedTarget) {
|
||||||
|
target = compressedTarget;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Find parent to add to
|
// Find parent to add to
|
||||||
if (!target) {
|
if (!target) {
|
||||||
target = this.explorerService.roots[this.explorerService.roots.length - 1];
|
target = this.explorerService.roots[this.explorerService.roots.length - 1];
|
||||||
|
@ -628,41 +790,43 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
|
|
||||||
// Desktop DND (Import file)
|
// Desktop DND (Import file)
|
||||||
if (data instanceof DesktopDragAndDropData) {
|
if (data instanceof DesktopDragAndDropData) {
|
||||||
this.handleExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e));
|
if (isWeb) {
|
||||||
|
this.handleWebExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e));
|
||||||
|
} else {
|
||||||
|
this.handleExternalDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// In-Explorer DND (Move/Copy file)
|
// In-Explorer DND (Move/Copy file)
|
||||||
else {
|
else {
|
||||||
this.handleExplorerDrop(data, target, originalEvent).then(undefined, e => this.notificationService.warn(e));
|
this.handleExplorerDrop(data as ElementsDragAndDropData<ExplorerItem, ExplorerItem[]>, target, originalEvent).then(undefined, e => this.notificationService.warn(e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
private async handleWebExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
||||||
if (isWeb) {
|
data.files.forEach(file => {
|
||||||
data.files.forEach(file => {
|
const reader = new FileReader();
|
||||||
const reader = new FileReader();
|
reader.readAsArrayBuffer(file);
|
||||||
reader.readAsArrayBuffer(file);
|
reader.onload = async (event) => {
|
||||||
reader.onload = async (event) => {
|
const name = file.name;
|
||||||
const name = file.name;
|
if (typeof name === 'string' && event.target?.result instanceof ArrayBuffer) {
|
||||||
if (typeof name === 'string' && event.target?.result instanceof ArrayBuffer) {
|
if (target.getChild(name)) {
|
||||||
if (target.getChild(name)) {
|
const { confirmed } = await this.dialogService.confirm(fileOverwriteConfirm(name));
|
||||||
const { confirmed } = await this.dialogService.confirm(fileOverwriteConfirm(name));
|
if (!confirmed) {
|
||||||
if (!confirmed) {
|
return;
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const resource = joinPath(target.resource, name);
|
|
||||||
await this.fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(event.target?.result)));
|
|
||||||
if (data.files.length === 1) {
|
|
||||||
await this.editorService.openEditor({ resource, options: { pinned: true } });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
const resource = joinPath(target.resource, name);
|
||||||
}
|
await this.fileService.writeFile(resource, VSBuffer.wrap(new Uint8Array(event.target?.result)));
|
||||||
|
if (data.files.length === 1) {
|
||||||
|
await this.editorService.openEditor({ resource, options: { pinned: true } });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleExternalDrop(data: DesktopDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
||||||
const droppedResources = extractResources(originalEvent, true);
|
const droppedResources = extractResources(originalEvent, true);
|
||||||
// Check for dropped external files to be folders
|
// Check for dropped external files to be folders
|
||||||
const result = await this.fileService.resolveAll(droppedResources);
|
const result = await this.fileService.resolveAll(droppedResources);
|
||||||
|
@ -755,8 +919,8 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async handleExplorerDrop(data: IDragAndDropData, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
private async handleExplorerDrop(data: ElementsDragAndDropData<ExplorerItem, ExplorerItem[]>, target: ExplorerItem, originalEvent: DragEvent): Promise<void> {
|
||||||
const elementsData = (data as ElementsDragAndDropData<ExplorerItem>).elements;
|
const elementsData = FileDragAndDrop.getStatsFromDragAndDropData(data);
|
||||||
const items = distinctParents(elementsData, s => s.resource);
|
const items = distinctParents(elementsData, s => s.resource);
|
||||||
const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh);
|
const isCopy = (originalEvent.ctrlKey && !isMacintosh) || (originalEvent.altKey && isMacintosh);
|
||||||
|
|
||||||
|
@ -869,6 +1033,66 @@ export class FileDragAndDrop implements ITreeDragAndDrop<ExplorerItem> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static getStatsFromDragAndDropData(data: ElementsDragAndDropData<ExplorerItem, ExplorerItem[]>, dragStartEvent?: DragEvent): ExplorerItem[] {
|
||||||
|
if (data.context) {
|
||||||
|
return data.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect compressed folder dragging
|
||||||
|
if (dragStartEvent && data.elements.length === 1) {
|
||||||
|
data.context = [FileDragAndDrop.getCompressedStatFromDragEvent(data.elements[0], dragStartEvent)];
|
||||||
|
return data.context;
|
||||||
|
}
|
||||||
|
|
||||||
|
return data.elements;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static getCompressedStatFromDragEvent(stat: ExplorerItem, dragEvent: DragEvent): ExplorerItem {
|
||||||
|
const target = document.elementFromPoint(dragEvent.clientX, dragEvent.clientY);
|
||||||
|
const iconLabelName = getIconLabelNameFromHTMLElement(target);
|
||||||
|
|
||||||
|
if (iconLabelName) {
|
||||||
|
const { count, index } = iconLabelName;
|
||||||
|
|
||||||
|
let i = count - 1;
|
||||||
|
while (i > index && stat.parent) {
|
||||||
|
stat = stat.parent;
|
||||||
|
i--;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
return stat;
|
||||||
|
}
|
||||||
|
|
||||||
|
onDragEnd(): void {
|
||||||
|
this.compressedDropTargetDisposable.dispose();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIconLabelNameFromHTMLElement(target: HTMLElement | EventTarget | Element | null): { element: HTMLElement, count: number, index: number } | null {
|
||||||
|
if (!(target instanceof HTMLElement)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
let element: HTMLElement | null = target;
|
||||||
|
|
||||||
|
while (element && !DOM.hasClass(element, 'monaco-list-row')) {
|
||||||
|
if (DOM.hasClass(element, 'label-name') && element.hasAttribute('data-icon-label-count')) {
|
||||||
|
const count = Number(element.getAttribute('data-icon-label-count'));
|
||||||
|
const index = Number(element.getAttribute('data-icon-label-index'));
|
||||||
|
|
||||||
|
if (isNumber(count) && isNumber(index)) {
|
||||||
|
return { element: element, count, index };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
element = element.parentElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExplorerCompressionDelegate implements ITreeCompressionDelegate<ExplorerItem> {
|
export class ExplorerCompressionDelegate implements ITreeCompressionDelegate<ExplorerItem> {
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
import { Event, Emitter } from 'vs/base/common/event';
|
import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
|
||||||
import { DisposableStore } from 'vs/base/common/lifecycle';
|
import { DisposableStore } from 'vs/base/common/lifecycle';
|
||||||
import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration } from 'vs/workbench/contrib/files/common/files';
|
import { IExplorerService, IEditableData, IFilesConfiguration, SortOrder, SortOrderConfiguration, IContextProvider } from 'vs/workbench/contrib/files/common/files';
|
||||||
import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
|
import { ExplorerItem, ExplorerModel } from 'vs/workbench/contrib/files/common/explorerModel';
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files';
|
import { FileOperationEvent, FileOperation, IFileStat, IFileService, FileChangesEvent, FILES_EXCLUDE_CONFIG, FileChangeType, IResolveFileOptions } from 'vs/platform/files/common/files';
|
||||||
|
@ -41,6 +41,7 @@ export class ExplorerService implements IExplorerService {
|
||||||
private _sortOrder: SortOrder;
|
private _sortOrder: SortOrder;
|
||||||
private cutItems: ExplorerItem[] | undefined;
|
private cutItems: ExplorerItem[] | undefined;
|
||||||
private fileSystemProviderSchemes = new Set<string>();
|
private fileSystemProviderSchemes = new Set<string>();
|
||||||
|
private contextProvider: IContextProvider | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@IFileService private fileService: IFileService,
|
@IFileService private fileService: IFileService,
|
||||||
|
@ -48,7 +49,7 @@ export class ExplorerService implements IExplorerService {
|
||||||
@IConfigurationService private configurationService: IConfigurationService,
|
@IConfigurationService private configurationService: IConfigurationService,
|
||||||
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
@IWorkspaceContextService private contextService: IWorkspaceContextService,
|
||||||
@IClipboardService private clipboardService: IClipboardService,
|
@IClipboardService private clipboardService: IClipboardService,
|
||||||
@IEditorService private editorService: IEditorService
|
@IEditorService private editorService: IEditorService,
|
||||||
) {
|
) {
|
||||||
this._sortOrder = this.configurationService.getValue('explorer.sortOrder');
|
this._sortOrder = this.configurationService.getValue('explorer.sortOrder');
|
||||||
}
|
}
|
||||||
|
@ -81,6 +82,18 @@ export class ExplorerService implements IExplorerService {
|
||||||
return this._sortOrder;
|
return this._sortOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
registerContextProvider(contextProvider: IContextProvider): void {
|
||||||
|
this.contextProvider = contextProvider;
|
||||||
|
}
|
||||||
|
|
||||||
|
getContext(respectMultiSelection: boolean): ExplorerItem[] {
|
||||||
|
if (!this.contextProvider) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.contextProvider.getContext(respectMultiSelection);
|
||||||
|
}
|
||||||
|
|
||||||
// Memoized locals
|
// Memoized locals
|
||||||
@memoize private get fileEventsFilter(): ResourceGlobMatcher {
|
@memoize private get fileEventsFilter(): ResourceGlobMatcher {
|
||||||
const fileEventsFilter = this.instantiationService.createInstance(
|
const fileEventsFilter = this.instantiationService.createInstance(
|
||||||
|
|
|
@ -51,6 +51,7 @@ export interface IExplorerService {
|
||||||
readonly onDidSelectResource: Event<{ resource?: URI, reveal?: boolean }>;
|
readonly onDidSelectResource: Event<{ resource?: URI, reveal?: boolean }>;
|
||||||
readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>;
|
readonly onDidCopyItems: Event<{ items: ExplorerItem[], cut: boolean, previouslyCutItems: ExplorerItem[] | undefined }>;
|
||||||
|
|
||||||
|
getContext(respectMultiSelection: boolean): ExplorerItem[];
|
||||||
setEditable(stat: ExplorerItem, data: IEditableData | null): void;
|
setEditable(stat: ExplorerItem, data: IEditableData | null): void;
|
||||||
getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined;
|
getEditable(): { stat: ExplorerItem, data: IEditableData } | undefined;
|
||||||
getEditableData(stat: ExplorerItem): IEditableData | undefined;
|
getEditableData(stat: ExplorerItem): IEditableData | undefined;
|
||||||
|
@ -66,7 +67,14 @@ export interface IExplorerService {
|
||||||
* Will try to resolve the path in case the explorer is not yet expanded to the file yet.
|
* Will try to resolve the path in case the explorer is not yet expanded to the file yet.
|
||||||
*/
|
*/
|
||||||
select(resource: URI, reveal?: boolean): Promise<void>;
|
select(resource: URI, reveal?: boolean): Promise<void>;
|
||||||
|
|
||||||
|
registerContextProvider(contextProvider: IContextProvider): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IContextProvider {
|
||||||
|
getContext(respectMultiSelection: boolean): ExplorerItem[];
|
||||||
|
}
|
||||||
|
|
||||||
export const IExplorerService = createDecorator<IExplorerService>('explorerService');
|
export const IExplorerService = createDecorator<IExplorerService>('explorerService');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -84,6 +92,11 @@ export const OpenEditorsVisibleContext = new RawContextKey<boolean>('openEditors
|
||||||
export const OpenEditorsFocusedContext = new RawContextKey<boolean>('openEditorsFocus', true);
|
export const OpenEditorsFocusedContext = new RawContextKey<boolean>('openEditorsFocus', true);
|
||||||
export const ExplorerFocusedContext = new RawContextKey<boolean>('explorerViewletFocus', true);
|
export const ExplorerFocusedContext = new RawContextKey<boolean>('explorerViewletFocus', true);
|
||||||
|
|
||||||
|
// compressed nodes
|
||||||
|
export const ExplorerCompressedFocusContext = new RawContextKey<boolean>('explorerViewletCompressedFocus', true);
|
||||||
|
export const ExplorerCompressedFirstFocusContext = new RawContextKey<boolean>('explorerViewletCompressedFirstFocus', true);
|
||||||
|
export const ExplorerCompressedLastFocusContext = new RawContextKey<boolean>('explorerViewletCompressedLastFocus', true);
|
||||||
|
|
||||||
export const FilesExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
|
export const FilesExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, FilesExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
|
||||||
export const ExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
|
export const ExplorerFocusCondition = ContextKeyExpr.and(ExplorerViewletVisibleContext, ExplorerFocusedContext, ContextKeyExpr.not(InputFocusedContextKey));
|
||||||
|
|
||||||
|
|
|
@ -47,6 +47,7 @@ import { ISetting, ISettingsGroup, SettingValueType } from 'vs/workbench/service
|
||||||
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
import { IWorkbenchEnvironmentService } from 'vs/workbench/services/environment/common/environmentService';
|
||||||
import { isArray } from 'vs/base/common/types';
|
import { isArray } from 'vs/base/common/types';
|
||||||
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
import { BrowserFeatures } from 'vs/base/browser/canIUse';
|
||||||
|
import { isIOS } from 'vs/base/common/platform';
|
||||||
|
|
||||||
const $ = DOM.$;
|
const $ = DOM.$;
|
||||||
|
|
||||||
|
@ -911,7 +912,7 @@ export class SettingEnumRenderer extends AbstractSettingRenderer implements ITre
|
||||||
const common = this.renderCommonTemplate(null, container, 'enum');
|
const common = this.renderCommonTemplate(null, container, 'enum');
|
||||||
|
|
||||||
const selectBox = new SelectBox([], 0, this._contextViewService, undefined, {
|
const selectBox = new SelectBox([], 0, this._contextViewService, undefined, {
|
||||||
useCustomDrawn: !BrowserFeatures.pointerEvents
|
useCustomDrawn: !(isIOS && BrowserFeatures.pointerEvents)
|
||||||
});
|
});
|
||||||
|
|
||||||
common.toDispose.push(selectBox);
|
common.toDispose.push(selectBox);
|
||||||
|
|
|
@ -135,6 +135,7 @@
|
||||||
.scm-viewlet .monaco-list .monaco-list-row .resource-group > .actions,
|
.scm-viewlet .monaco-list .monaco-list-row .resource-group > .actions,
|
||||||
.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
.scm-viewlet .monaco-list .monaco-list-row .resource > .name > .monaco-icon-label > .actions {
|
||||||
display: none;
|
display: none;
|
||||||
|
max-width: fit-content;
|
||||||
}
|
}
|
||||||
|
|
||||||
.scm-viewlet .monaco-list .monaco-list-row:hover .resource-group > .actions,
|
.scm-viewlet .monaco-list .monaco-list-row:hover .resource-group > .actions,
|
||||||
|
|
|
@ -8,11 +8,15 @@ import { Event, Emitter } from 'vs/base/common/event';
|
||||||
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
|
import { IAuthTokenService, AuthTokenStatus } from 'vs/platform/auth/common/auth';
|
||||||
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
import { ICredentialsService } from 'vs/platform/credentials/common/credentials';
|
||||||
import { Disposable } from 'vs/base/common/lifecycle';
|
import { Disposable } from 'vs/base/common/lifecycle';
|
||||||
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
|
||||||
import { URI } from 'vs/base/common/uri';
|
import { URI } from 'vs/base/common/uri';
|
||||||
|
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
|
||||||
|
import { IQuickInputService } from 'vs/platform/quickinput/common/quickInput';
|
||||||
|
import { INotificationService } from 'vs/platform/notification/common/notification';
|
||||||
|
import { UserAgentApplication } from 'Msal';
|
||||||
|
|
||||||
const SERVICE_NAME = 'VS Code';
|
const SERVICE_NAME = 'VS Code';
|
||||||
const ACCOUNT = 'MyAccount';
|
const ACCOUNT = 'MyAccount';
|
||||||
|
const clientId = 'aebc6443-996d-45c2-90f0-388ff96faa56';
|
||||||
|
|
||||||
export class AuthTokenService extends Disposable implements IAuthTokenService {
|
export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||||
_serviceBrand: undefined;
|
_serviceBrand: undefined;
|
||||||
|
@ -24,15 +28,25 @@ export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||||
|
|
||||||
readonly _onDidGetCallback: Emitter<URI> = this._register(new Emitter<URI>());
|
readonly _onDidGetCallback: Emitter<URI> = this._register(new Emitter<URI>());
|
||||||
|
|
||||||
|
private _msalInstance: UserAgentApplication | undefined;
|
||||||
|
private _loadMsal: Promise<void>;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
@ICredentialsService private readonly credentialsService: ICredentialsService,
|
@ICredentialsService private readonly credentialsService: ICredentialsService,
|
||||||
@IQuickInputService private readonly quickInputService: IQuickInputService
|
@IEnvironmentService private readonly environmentService: IEnvironmentService,
|
||||||
|
@IQuickInputService private readonly quickInputService: IQuickInputService,
|
||||||
|
@INotificationService private readonly notificationService: INotificationService
|
||||||
) {
|
) {
|
||||||
super();
|
super();
|
||||||
this._status = AuthTokenStatus.Inactive;
|
this._loadMsal = (import('Msal')).then(msal => {
|
||||||
|
this._msalInstance = new msal.UserAgentApplication({ auth: { clientId } });
|
||||||
|
});
|
||||||
|
|
||||||
this.getToken().then(token => {
|
this.getToken().then(token => {
|
||||||
if (token) {
|
if (token) {
|
||||||
this.setStatus(AuthTokenStatus.Active);
|
this.setStatus(AuthTokenStatus.Active);
|
||||||
|
} else {
|
||||||
|
this.setStatus(AuthTokenStatus.Inactive);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -41,16 +55,40 @@ export class AuthTokenService extends Disposable implements IAuthTokenService {
|
||||||
const token = await this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT);
|
const token = await this.credentialsService.getPassword(SERVICE_NAME, ACCOUNT);
|
||||||
if (token) {
|
if (token) {
|
||||||
return token;
|
return token;
|
||||||
}
|
} else {
|
||||||
|
if (!this.environmentService.isBuilt) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
try {
|
||||||
|
await this._loadMsal;
|
||||||
|
const response = await this._msalInstance!.acquireTokenSilent({});
|
||||||
|
return response.accessToken;
|
||||||
|
} catch (e) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async login(): Promise<void> {
|
async login(): Promise<void> {
|
||||||
const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, });
|
// Cannot redirect to localhost in the implicit grant flow, fall back to asking for token when running out of sources
|
||||||
if (token) {
|
if (!this.environmentService.isBuilt) {
|
||||||
await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token);
|
const token = await this.quickInputService.input({ placeHolder: localize('enter token', "Please provide the auth bearer token"), ignoreFocusLost: true, });
|
||||||
|
if (token) {
|
||||||
|
await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, token);
|
||||||
|
this.setStatus(AuthTokenStatus.Active);
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await this._loadMsal;
|
||||||
|
const response = await this._msalInstance!.loginPopup();
|
||||||
|
await this.credentialsService.setPassword(SERVICE_NAME, ACCOUNT, response.accessToken);
|
||||||
this.setStatus(AuthTokenStatus.Active);
|
this.setStatus(AuthTokenStatus.Active);
|
||||||
|
} catch (e) {
|
||||||
|
this.notificationService.error(localize('loginFailed', "Login failed: {0}", e));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -472,7 +472,8 @@
|
||||||
"**/vs/workbench/api/{common,browser}/**",
|
"**/vs/workbench/api/{common,browser}/**",
|
||||||
"**/vs/workbench/services/**/{common,browser}/**",
|
"**/vs/workbench/services/**/{common,browser}/**",
|
||||||
"vscode-textmate",
|
"vscode-textmate",
|
||||||
"onigasm-umd"
|
"onigasm-umd",
|
||||||
|
"Msal"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|
12
yarn.lock
12
yarn.lock
|
@ -5649,6 +5649,13 @@ ms@^2.1.1:
|
||||||
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a"
|
||||||
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==
|
||||||
|
|
||||||
|
msal@^1.1.3:
|
||||||
|
version "1.1.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/msal/-/msal-1.1.3.tgz#d8c501d513f5e52eaf000f20a245526fc1ecaa2a"
|
||||||
|
integrity sha512-cdShb+N1H3OyR1y46ij6OO7QzeqC6BxrbrNcouS4JBrr1+DnZ55TumxQKEzWmTXHvsbsuz5PCyXZl812Un8L9g==
|
||||||
|
dependencies:
|
||||||
|
tslib "^1.9.3"
|
||||||
|
|
||||||
multimatch@^2.0.0:
|
multimatch@^2.0.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b"
|
resolved "https://registry.yarnpkg.com/multimatch/-/multimatch-2.1.0.tgz#9c7906a22fb4c02919e2f5f75161b4cdbd4b2a2b"
|
||||||
|
@ -8504,6 +8511,11 @@ tslib@^1.8.1, tslib@^1.9.0:
|
||||||
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286"
|
||||||
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ==
|
||||||
|
|
||||||
|
tslib@^1.9.3:
|
||||||
|
version "1.10.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.10.0.tgz#c3c19f95973fb0a62973fb09d90d961ee43e5c8a"
|
||||||
|
integrity sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==
|
||||||
|
|
||||||
tslint@^5.16.0:
|
tslint@^5.16.0:
|
||||||
version "5.16.0"
|
version "5.16.0"
|
||||||
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.16.0.tgz#ae61f9c5a98d295b9a4f4553b1b1e831c1984d67"
|
resolved "https://registry.yarnpkg.com/tslint/-/tslint-5.16.0.tgz#ae61f9c5a98d295b9a4f4553b1b1e831c1984d67"
|
||||||
|
|
Loading…
Reference in a new issue