提交 6cba5616 编写于 作者: C Christof Marti

Writeable active and selected items (#49340, fixes vscode-azure-account#67)

上级 05b5db7a
...@@ -9,6 +9,13 @@ import * as assert from 'assert'; ...@@ -9,6 +9,13 @@ import * as assert from 'assert';
import { window, commands } from 'vscode'; import { window, commands } from 'vscode';
import { closeAllEditors } from '../utils'; import { closeAllEditors } from '../utils';
interface QuickPickExpected {
events: string[];
activeItems: string[][];
selectionItems: string[][];
acceptedItems: string[][];
}
suite('window namespace tests', function () { suite('window namespace tests', function () {
suite('QuickInput tests', function () { suite('QuickInput tests', function () {
...@@ -20,59 +27,87 @@ suite('window namespace tests', function () { ...@@ -20,59 +27,87 @@ suite('window namespace tests', function () {
_done(err); _done(err);
}; };
const expectedEvents = ['active', 'active', 'selection', 'accept', 'hide']; const quickPick = createQuickPick({
const expectedActiveItems = [['eins'], ['zwei']]; events: ['active', 'active', 'selection', 'accept', 'hide'],
const expectedSelectionItems = [['zwei']]; activeItems: [['eins'], ['zwei']],
selectionItems: [['zwei']],
acceptedItems: [['zwei']],
}, done);
quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label }));
quickPick.show();
const quickPick = window.createQuickPick(); (async () => {
quickPick.onDidChangeActive(items => { await commands.executeCommand('workbench.action.quickOpenSelectNext');
try { await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
assert.equal('active', expectedEvents.shift()); })()
const expected = expectedActiveItems.shift(); .catch(err => done(err));
assert.deepEqual(items.map(item => item.label), expected); });
assert.deepEqual(quickPick.activeItems.map(item => item.label), expected);
} catch (err) {
done(err);
}
});
quickPick.onDidChangeSelection(items => {
try {
assert.equal('selection', expectedEvents.shift());
const expected = expectedSelectionItems.shift();
assert.deepEqual(items.map(item => item.label), expected);
assert.deepEqual(quickPick.selectedItems.map(item => item.label), expected);
} catch (err) {
done(err);
}
});
quickPick.onDidAccept(() => {
try {
assert.equal('accept', expectedEvents.shift());
const expected = ['zwei'];
assert.deepEqual(quickPick.activeItems.map(item => item.label), expected);
assert.deepEqual(quickPick.selectedItems.map(item => item.label), expected);
quickPick.dispose();
} catch (err) {
done(err);
}
});
quickPick.onDidHide(() => {
try {
assert.equal('hide', expectedEvents.shift());
done();
} catch (err) {
done(err);
}
});
test('createQuickPick, focus second', function (_done) {
let done = (err?: any) => {
done = () => {};
_done(err);
};
const quickPick = createQuickPick({
events: ['active', 'selection', 'accept', 'hide'],
activeItems: [['zwei']],
selectionItems: [['zwei']],
acceptedItems: [['zwei']],
}, done);
quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label })); quickPick.items = ['eins', 'zwei', 'drei'].map(label => ({ label }));
quickPick.activeItems = [quickPick.items[1]];
quickPick.show(); quickPick.show();
(async () => { (async () => {
await commands.executeCommand('workbench.action.quickOpenSelectNext');
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem'); await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
})() })()
.catch(err => done(err)); .catch(err => done(err));
}); });
}); });
}); });
function createQuickPick(expected: QuickPickExpected, done: (err?: any) => void) {
const quickPick = window.createQuickPick();
quickPick.onDidChangeActive(items => {
try {
assert.equal('active', expected.events.shift());
const expectedItems = expected.activeItems.shift();
assert.deepEqual(items.map(item => item.label), expectedItems);
assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedItems);
} catch (err) {
done(err);
}
});
quickPick.onDidChangeSelection(items => {
try {
assert.equal('selection', expected.events.shift());
const expectedItems = expected.selectionItems.shift();
assert.deepEqual(items.map(item => item.label), expectedItems);
assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedItems);
} catch (err) {
done(err);
}
});
quickPick.onDidAccept(() => {
try {
assert.equal('accept', expected.events.shift());
const expectedItems = expected.acceptedItems.shift();
assert.deepEqual(quickPick.activeItems.map(item => item.label), expectedItems);
assert.deepEqual(quickPick.selectedItems.map(item => item.label), expectedItems);
quickPick.dispose();
} catch (err) {
done(err);
}
});
quickPick.onDidHide(() => {
try {
assert.equal('hide', expected.events.shift());
done();
} catch (err) {
done(err);
}
});
return quickPick;
}
\ No newline at end of file
...@@ -440,6 +440,18 @@ suite('window namespace tests', () => { ...@@ -440,6 +440,18 @@ suite('window namespace tests', () => {
assert.deepStrictEqual(await picks, ['eins', 'zwei']); assert.deepStrictEqual(await picks, ['eins', 'zwei']);
}); });
test('showQuickPick, keep selection (Microsoft/vscode-azure-account#67)', async function () {
const picks = window.showQuickPick([
{ label: 'eins' },
{ label: 'zwei', picked: true },
{ label: 'drei', picked: true }
], {
canPickMany: true
});
await commands.executeCommand('workbench.action.acceptSelectedQuickOpenItem');
assert.deepStrictEqual((await picks)!.map(pick => pick.label), ['zwei', 'drei']);
});
test('showQuickPick, undefined on cancel', function () { test('showQuickPick, undefined on cancel', function () {
const source = new CancellationTokenSource(); const source = new CancellationTokenSource();
const p = window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token); const p = window.showQuickPick(['eins', 'zwei', 'drei'], undefined, source.token);
......
...@@ -131,11 +131,11 @@ export interface IQuickPick extends IQuickInput { ...@@ -131,11 +131,11 @@ export interface IQuickPick extends IQuickInput {
matchOnDetail: boolean; matchOnDetail: boolean;
readonly activeItems: ReadonlyArray<IQuickPickItem>; activeItems: ReadonlyArray<IQuickPickItem>;
readonly onDidChangeActive: Event<IQuickPickItem[]>; readonly onDidChangeActive: Event<IQuickPickItem[]>;
readonly selectedItems: ReadonlyArray<IQuickPickItem>; selectedItems: ReadonlyArray<IQuickPickItem>;
readonly onDidChangeSelection: Event<IQuickPickItem[]>; readonly onDidChangeSelection: Event<IQuickPickItem[]>;
} }
......
...@@ -581,11 +581,11 @@ declare module 'vscode' { ...@@ -581,11 +581,11 @@ declare module 'vscode' {
matchOnDetail: boolean; matchOnDetail: boolean;
readonly activeItems: ReadonlyArray<QuickPickItem>; activeItems: ReadonlyArray<QuickPickItem>;
readonly onDidChangeActive: Event<QuickPickItem[]>; readonly onDidChangeActive: Event<QuickPickItem[]>;
readonly selectedItems: ReadonlyArray<QuickPickItem>; selectedItems: ReadonlyArray<QuickPickItem>;
readonly onDidChangeSelection: Event<QuickPickItem[]>; readonly onDidChangeSelection: Event<QuickPickItem[]>;
} }
......
...@@ -12,6 +12,11 @@ import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, Transf ...@@ -12,6 +12,11 @@ import { ExtHostContext, MainThreadQuickOpenShape, ExtHostQuickOpenShape, Transf
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers'; import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import URI from 'vs/base/common/uri'; import URI from 'vs/base/common/uri';
interface QuickInputSession {
input: IQuickInput;
handlesToItems: Map<number, TransferQuickPickItems>;
}
@extHostNamedCustomer(MainContext.MainThreadQuickOpen) @extHostNamedCustomer(MainContext.MainThreadQuickOpen)
export class MainThreadQuickOpen implements MainThreadQuickOpenShape { export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
...@@ -114,7 +119,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { ...@@ -114,7 +119,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
// ---- QuickInput // ---- QuickInput
private sessions = new Map<number, IQuickInput>(); private sessions = new Map<number, QuickInputSession>();
$createOrUpdate(params: TransferQuickInput): TPromise<void> { $createOrUpdate(params: TransferQuickInput): TPromise<void> {
const sessionId = params.id; const sessionId = params.id;
...@@ -140,7 +145,10 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { ...@@ -140,7 +145,10 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
input.onDidHide(() => { input.onDidHide(() => {
this._proxy.$onDidHide(sessionId); this._proxy.$onDidHide(sessionId);
}); });
session = input; session = {
input,
handlesToItems: new Map()
};
} else { } else {
const input = this._quickInputService.createInputBox(); const input = this._quickInputService.createInputBox();
input.onDidAccept(() => { input.onDidAccept(() => {
...@@ -155,22 +163,36 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { ...@@ -155,22 +163,36 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
input.onDidHide(() => { input.onDidHide(() => {
this._proxy.$onDidHide(sessionId); this._proxy.$onDidHide(sessionId);
}); });
session = input; session = {
input,
handlesToItems: new Map()
};
} }
this.sessions.set(sessionId, session); this.sessions.set(sessionId, session);
} }
const { input, handlesToItems } = session;
for (const param in params) { for (const param in params) {
if (param === 'id' || param === 'type') { if (param === 'id' || param === 'type') {
continue; continue;
} }
if (param === 'visible') { if (param === 'visible') {
if (params.visible) { if (params.visible) {
session.show(); input.show();
} else { } else {
session.hide(); input.hide();
} }
} else if (param === 'items') {
handlesToItems.clear();
params[param].forEach(item => {
handlesToItems.set(item.handle, item);
});
input[param] = params[param];
} else if (param === 'activeItems' || param === 'selectedItems') {
input[param] = params[param]
.filter(handle => handlesToItems.has(handle))
.map(handle => handlesToItems.get(handle));
} else if (param === 'buttons') { } else if (param === 'buttons') {
session[param] = params.buttons.map(button => { input[param] = params.buttons.map(button => {
if (button.handle === -1) { if (button.handle === -1) {
return this._quickInputService.backButton; return this._quickInputService.backButton;
} }
...@@ -185,7 +207,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { ...@@ -185,7 +207,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
}; };
}); });
} else { } else {
session[param] = params[param]; input[param] = params[param];
} }
} }
return TPromise.as(undefined); return TPromise.as(undefined);
...@@ -194,7 +216,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape { ...@@ -194,7 +216,7 @@ export class MainThreadQuickOpen implements MainThreadQuickOpenShape {
$dispose(sessionId: number): TPromise<void> { $dispose(sessionId: number): TPromise<void> {
const session = this.sessions.get(sessionId); const session = this.sessions.get(sessionId);
if (session) { if (session) {
session.dispose(); session.input.dispose();
this.sessions.delete(sessionId); this.sessions.delete(sessionId);
} }
return TPromise.as(undefined); return TPromise.as(undefined);
......
...@@ -380,6 +380,10 @@ export interface TransferQuickPick extends BaseTransferQuickInput { ...@@ -380,6 +380,10 @@ export interface TransferQuickPick extends BaseTransferQuickInput {
items?: TransferQuickPickItems[]; items?: TransferQuickPickItems[];
activeItems?: number[];
selectedItems?: number[];
canSelectMany?: boolean; canSelectMany?: boolean;
ignoreFocusOut?: boolean; ignoreFocusOut?: boolean;
......
...@@ -455,6 +455,7 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick { ...@@ -455,6 +455,7 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick {
private _items: QuickPickItem[] = []; private _items: QuickPickItem[] = [];
private _handlesToItems = new Map<number, QuickPickItem>(); private _handlesToItems = new Map<number, QuickPickItem>();
private _itemsToHandles = new Map<QuickPickItem, number>();
private _canSelectMany = false; private _canSelectMany = false;
private _matchOnDescription = true; private _matchOnDescription = true;
private _matchOnDetail = true; private _matchOnDetail = true;
...@@ -479,8 +480,10 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick { ...@@ -479,8 +480,10 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick {
set items(items: QuickPickItem[]) { set items(items: QuickPickItem[]) {
this._items = items; this._items = items;
this._handlesToItems.clear(); this._handlesToItems.clear();
this._itemsToHandles.clear();
items.forEach((item, i) => { items.forEach((item, i) => {
this._handlesToItems.set(i, item); this._handlesToItems.set(i, item);
this._itemsToHandles.set(item, i);
}); });
this.update({ this.update({
items: items.map((item, i) => ({ items: items.map((item, i) => ({
...@@ -524,12 +527,22 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick { ...@@ -524,12 +527,22 @@ class ExtHostQuickPick extends ExtHostQuickInput implements QuickPick {
return this._activeItems; return this._activeItems;
} }
set activeItems(activeItems: QuickPickItem[]) {
this._activeItems = activeItems.filter(item => this._itemsToHandles.has(item));
this.update({ activeItems: this._activeItems.map(item => this._itemsToHandles.get(item)) });
}
onDidChangeActive = this._onDidChangeActiveEmitter.event; onDidChangeActive = this._onDidChangeActiveEmitter.event;
get selectedItems() { get selectedItems() {
return this._selectedItems; return this._selectedItems;
} }
set selectedItems(selectedItems: QuickPickItem[]) {
this._selectedItems = selectedItems.filter(item => this._itemsToHandles.has(item));
this.update({ selectedItems: this._selectedItems.map(item => this._itemsToHandles.get(item)) });
}
onDidChangeSelection = this._onDidChangeSelectionEmitter.event; onDidChangeSelection = this._onDidChangeSelectionEmitter.event;
_fireDidChangeActive(handles: number[]) { _fireDidChangeActive(handles: number[]) {
......
...@@ -266,8 +266,10 @@ class QuickPick extends QuickInput implements IQuickPick { ...@@ -266,8 +266,10 @@ class QuickPick extends QuickInput implements IQuickPick {
private _matchOnDescription = true; private _matchOnDescription = true;
private _matchOnDetail = true; private _matchOnDetail = true;
private _activeItems: IQuickPickItem[] = []; private _activeItems: IQuickPickItem[] = [];
private activeItemsUpdated = false;
private onDidChangeActiveEmitter = new Emitter<IQuickPickItem[]>(); private onDidChangeActiveEmitter = new Emitter<IQuickPickItem[]>();
private _selectedItems: IQuickPickItem[] = []; private _selectedItems: IQuickPickItem[] = [];
private selectedItemsUpdated = false;
private onDidChangeSelectionEmitter = new Emitter<IQuickPickItem[]>(); private onDidChangeSelectionEmitter = new Emitter<IQuickPickItem[]>();
private quickNavigate = false; private quickNavigate = false;
...@@ -344,12 +346,24 @@ class QuickPick extends QuickInput implements IQuickPick { ...@@ -344,12 +346,24 @@ class QuickPick extends QuickInput implements IQuickPick {
return this._activeItems; return this._activeItems;
} }
set activeItems(activeItems: IQuickPickItem[]) {
this._activeItems = activeItems;
this.activeItemsUpdated = true;
this.update();
}
onDidChangeActive = this.onDidChangeActiveEmitter.event; onDidChangeActive = this.onDidChangeActiveEmitter.event;
get selectedItems() { get selectedItems() {
return this._selectedItems; return this._selectedItems;
} }
set selectedItems(selectedItems: IQuickPickItem[]) {
this._selectedItems = selectedItems;
this.selectedItemsUpdated = true;
this.update();
}
onDidChangeSelection = this.onDidChangeSelectionEmitter.event; onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
show() { show() {
...@@ -390,6 +404,9 @@ class QuickPick extends QuickInput implements IQuickPick { ...@@ -390,6 +404,9 @@ class QuickPick extends QuickInput implements IQuickPick {
this.onDidAcceptEmitter.fire(); this.onDidAcceptEmitter.fire();
}), }),
this.ui.list.onDidChangeFocus(focusedItems => { this.ui.list.onDidChangeFocus(focusedItems => {
if (this.activeItemsUpdated) {
return; // Expect another event.
}
// Drop initial event. // Drop initial event.
if (!focusedItems.length && !this._activeItems.length) { if (!focusedItems.length && !this._activeItems.length) {
return; return;
...@@ -433,6 +450,7 @@ class QuickPick extends QuickInput implements IQuickPick { ...@@ -433,6 +450,7 @@ class QuickPick extends QuickInput implements IQuickPick {
this.ui.inputBox.placeholder = (this.placeholder || ''); this.ui.inputBox.placeholder = (this.placeholder || '');
} }
if (this.itemsUpdated) { if (this.itemsUpdated) {
this.itemsUpdated = false;
this.ui.list.setElements(this.items); this.ui.list.setElements(this.items);
this.ui.list.filter(this.ui.inputBox.value); this.ui.list.filter(this.ui.inputBox.value);
this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked(); this.ui.checkAll.checked = this.ui.list.getAllVisibleChecked();
...@@ -440,7 +458,6 @@ class QuickPick extends QuickInput implements IQuickPick { ...@@ -440,7 +458,6 @@ class QuickPick extends QuickInput implements IQuickPick {
if (!this.canSelectMany) { if (!this.canSelectMany) {
this.ui.list.focus('First'); this.ui.list.focus('First');
} }
this.itemsUpdated = false;
} }
if (this.ui.container.classList.contains('show-checkboxes') !== this.canSelectMany) { if (this.ui.container.classList.contains('show-checkboxes') !== this.canSelectMany) {
if (this.canSelectMany) { if (this.canSelectMany) {
...@@ -449,6 +466,18 @@ class QuickPick extends QuickInput implements IQuickPick { ...@@ -449,6 +466,18 @@ class QuickPick extends QuickInput implements IQuickPick {
this.ui.list.focus('First'); this.ui.list.focus('First');
} }
} }
if (this.activeItemsUpdated) {
this.activeItemsUpdated = false;
this.ui.list.setFocusedElements(this.activeItems);
}
if (this.selectedItemsUpdated) {
this.selectedItemsUpdated = false;
if (this.canSelectMany) {
this.ui.list.setCheckedElements(this.selectedItems);
} else {
this.ui.list.setSelectedElements(this.selectedItems);
}
}
this.ui.ignoreFocusOut = this.ignoreFocusOut; this.ui.ignoreFocusOut = this.ignoreFocusOut;
this.ui.list.matchOnDescription = this.matchOnDescription; this.ui.list.matchOnDescription = this.matchOnDescription;
this.ui.list.matchOnDetail = this.matchOnDetail; this.ui.list.matchOnDetail = this.matchOnDetail;
...@@ -878,6 +907,9 @@ export class QuickInputService extends Component implements IQuickInputService { ...@@ -878,6 +907,9 @@ export class QuickInputService extends Component implements IQuickInputService {
picks.then(items => { picks.then(items => {
input.busy = false; input.busy = false;
input.items = items; input.items = items;
if (input.canSelectMany) {
input.selectedItems = items.filter(item => item.picked);
}
}); });
input.show(); input.show();
picks.then(null, err => { picks.then(null, err => {
......
...@@ -149,6 +149,7 @@ export class QuickInputList { ...@@ -149,6 +149,7 @@ export class QuickInputList {
private container: HTMLElement; private container: HTMLElement;
private list: WorkbenchList<ListElement>; private list: WorkbenchList<ListElement>;
private elements: ListElement[] = []; private elements: ListElement[] = [];
private elementsToIndexes = new Map<IQuickPickItem, number>();
matchOnDescription = false; matchOnDescription = false;
matchOnDetail = false; matchOnDetail = false;
private _onChangedAllVisibleChecked = new Emitter<boolean>(); private _onChangedAllVisibleChecked = new Emitter<boolean>();
...@@ -269,9 +270,14 @@ export class QuickInputList { ...@@ -269,9 +270,14 @@ export class QuickInputList {
this.elements = elements.map((item, index) => new ListElement({ this.elements = elements.map((item, index) => new ListElement({
index, index,
item, item,
checked: !!item.picked checked: false
})); }));
this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents()))); this.elementDisposables.push(...this.elements.map(element => element.onChecked(() => this.fireCheckedEvents())));
this.elementsToIndexes = this.elements.reduce((map, element, index) => {
map.set(element.item, index);
return map;
}, new Map<IQuickPickItem, number>());
this.list.splice(0, this.list.length, this.elements); this.list.splice(0, this.list.length, this.elements);
this.list.setFocus([]); this.list.setFocus([]);
} }
...@@ -281,16 +287,44 @@ export class QuickInputList { ...@@ -281,16 +287,44 @@ export class QuickInputList {
.map(e => e.item); .map(e => e.item);
} }
setFocusedElements(items: IQuickPickItem[]) {
this.list.setFocus(items
.filter(item => this.elementsToIndexes.has(item))
.map(item => this.elementsToIndexes.get(item)));
}
getSelectedElements() { getSelectedElements() {
return this.list.getSelectedElements() return this.list.getSelectedElements()
.map(e => e.item); .map(e => e.item);
} }
setSelectedElements(items: IQuickPickItem[]) {
this.list.setSelection(items
.filter(item => this.elementsToIndexes.has(item))
.map(item => this.elementsToIndexes.get(item)));
}
getCheckedElements() { getCheckedElements() {
return this.elements.filter(e => e.checked) return this.elements.filter(e => e.checked)
.map(e => e.item); .map(e => e.item);
} }
setCheckedElements(items: IQuickPickItem[]) {
try {
this._fireCheckedEvents = false;
const checked = new Set();
for (const item of items) {
checked.add(item);
}
for (const element of this.elements) {
element.checked = checked.has(element.item);
}
} finally {
this._fireCheckedEvents = true;
this.fireCheckedEvents();
}
}
set enabled(value: boolean) { set enabled(value: boolean) {
this.list.getHTMLElement().style.pointerEvents = value ? null : 'none'; this.list.getHTMLElement().style.pointerEvents = value ? null : 'none';
} }
...@@ -368,6 +402,10 @@ export class QuickInputList { ...@@ -368,6 +402,10 @@ export class QuickInputList {
return compareEntries(a, b, normalizedSearchValue); return compareEntries(a, b, normalizedSearchValue);
}); });
this.elementsToIndexes = shownElements.reduce((map, element, index) => {
map.set(element.item, index);
return map;
}, new Map<IQuickPickItem, number>());
this.list.splice(0, this.list.length, shownElements); this.list.splice(0, this.list.length, shownElements);
this.list.setFocus([]); this.list.setFocus([]);
this.list.layout(); this.list.layout();
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册