Merge remote-tracking branch 'upstream/master' into rebornix/viewportevent

This commit is contained in:
Peng Lyu 2019-11-15 14:02:14 -08:00
commit 18f2fd24c4
35 changed files with 991 additions and 299 deletions

View file

@ -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",

View file

@ -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",

View file

@ -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",

View file

@ -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"

View file

@ -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"

View file

@ -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)
}; };

View file

@ -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

View file

@ -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),

View file

@ -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;
} }

View file

@ -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));
}
}
}
}
}

View file

@ -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;
} }

View file

@ -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 {

View file

@ -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 {

View file

@ -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);
} }

View file

@ -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 {

View file

@ -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);
} }

View file

@ -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;

View file

@ -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>

View file

@ -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>

View file

@ -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));

View file

@ -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);

View file

@ -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;

View file

@ -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();
} }
})); }));

View file

@ -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 }
})));
}
}
}; };

View file

@ -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();
}
});

View file

@ -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;

View file

@ -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();

View file

@ -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> {

View file

@ -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(

View file

@ -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));

View file

@ -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);

View file

@ -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,

View file

@ -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));
} }
} }

View file

@ -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"
] ]
}, },
{ {

View file

@ -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"