Skip to content

Commit 9cfc937

Browse files
committed
Dialog tweaks
1 parent 6286055 commit 9cfc937

8 files changed

Lines changed: 80 additions & 27 deletions

File tree

src/vs/platform/quickinput/common/quickInput.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ export interface IQuickPick<T extends IQuickPickItem> extends IQuickInput {
191191
readonly onDidChangeSelection: Event<T[]>;
192192

193193
readonly keyMods: IKeyMods;
194+
195+
valueSelection: Readonly<[number, number]> | undefined;
194196
}
195197

196198
export interface IInputBox extends IQuickInput {

src/vs/workbench/browser/parts/quickinput/quickInput.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,6 +327,8 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
327327
private selectedItemsToConfirm: T[] | null = [];
328328
private onDidChangeSelectionEmitter = new Emitter<T[]>();
329329
private onDidTriggerItemButtonEmitter = new Emitter<IQuickPickItemButtonEvent<T>>();
330+
private _valueSelection: Readonly<[number, number]>;
331+
private valueSelectionUpdated = true;
330332

331333
quickNavigate: IQuickNavigateConfiguration;
332334

@@ -444,6 +446,12 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
444446
return this.ui.keyMods;
445447
}
446448

449+
set valueSelection(valueSelection: Readonly<[number, number]>) {
450+
this._valueSelection = valueSelection;
451+
this.valueSelectionUpdated = true;
452+
this.update();
453+
}
454+
447455
onDidChangeSelection = this.onDidChangeSelectionEmitter.event;
448456

449457
onDidTriggerItemButton = this.onDidTriggerItemButtonEmitter.event;
@@ -559,6 +567,7 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
559567
this.ui.list.onButtonTriggered(event => this.onDidTriggerItemButtonEmitter.fire(event as IQuickPickItemButtonEvent<T>)),
560568
this.registerQuickNavigation()
561569
);
570+
this.valueSelectionUpdated = true;
562571
}
563572
super.show(); // TODO: Why have show() bubble up while update() trickles down? (Could move setComboboxAccessibility() here.)
564573
}
@@ -619,6 +628,10 @@ class QuickPick<T extends IQuickPickItem> extends QuickInput implements IQuickPi
619628
if (this.ui.inputBox.value !== this.value) {
620629
this.ui.inputBox.value = this.value;
621630
}
631+
if (this.valueSelectionUpdated) {
632+
this.valueSelectionUpdated = false;
633+
this.ui.inputBox.select(this._valueSelection && { start: this._valueSelection[0], end: this._valueSelection[1] });
634+
}
622635
if (this.ui.inputBox.placeholder !== (this.placeholder || '')) {
623636
this.ui.inputBox.placeholder = (this.placeholder || '');
624637
}

src/vs/workbench/browser/parts/quickinput/quickInputList.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -472,6 +472,9 @@ export class QuickInputList {
472472
}
473473

474474
filter(query: string) {
475+
if (!(this.matchOnLabel || this.matchOnDescription || this.matchOnDetail)) {
476+
return;
477+
}
475478
query = query.trim();
476479

477480
// Reset filtering
@@ -487,7 +490,7 @@ export class QuickInputList {
487490
}
488491

489492
// Filter by value (since we support octicons, use octicon aware fuzzy matching)
490-
else if (this.matchOnLabel || this.matchOnDescription || this.matchOnDetail) {
493+
else {
491494
this.elements.forEach(element => {
492495
const labelHighlights = this.matchOnLabel ? matchesFuzzyOcticonAware(query, parseOcticons(element.saneLabel)) || undefined : undefined;
493496
const descriptionHighlights = this.matchOnDescription ? matchesFuzzyOcticonAware(query, parseOcticons(element.saneDescription || '')) || undefined : undefined;
Lines changed: 1 addition & 0 deletions
Loading

src/vs/workbench/services/dialogs/media/dark/Folder.svg renamed to src/vs/workbench/services/dialogs/electron-browser/media/dark/folder.svg

File renamed without changes.
Lines changed: 1 addition & 0 deletions
Loading

src/vs/workbench/services/dialogs/media/light/Folder_inverse.svg renamed to src/vs/workbench/services/dialogs/electron-browser/media/light/folder.svg

File renamed without changes.

src/vs/workbench/services/dialogs/electron-browser/remoteFileDialog.ts

Lines changed: 59 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,15 @@ const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
3131
const WINDOWS_FORBIDDEN_NAMES = /^(con|prn|aux|clock\$|nul|lpt[0-9]|com[0-9])$/i;
3232

3333
export class RemoteFileDialog {
34-
private fallbackPickerButton = { iconPath: this.getAlternateDialogIcons(), tooltip: 'Use Alternate File System' };
35-
34+
private fallbackPickerButton;
35+
private acceptButton = { iconPath: this.getDialogIcons('accept'), tooltip: nls.localize('remoteFileDialog.accept', 'Select Item') };
3636
private currentFolder: URI;
3737
private filePickBox: IQuickPick<FileQuickPickItem>;
3838
private allowFileSelection: boolean;
3939
private allowFolderSelection: boolean;
4040
private remoteAuthority: string | undefined;
4141
private requiresTrailing: boolean;
42+
private userValue: string;
4243

4344
constructor(
4445
@IFileService private readonly remoteFileService: RemoteFileService,
@@ -61,6 +62,11 @@ export class RemoteFileDialog {
6162
return Promise.resolve(undefined);
6263
}
6364

65+
const openFileString = nls.localize('remoteFileDialog.localFileFallback', 'Open Local File');
66+
const openFolderString = nls.localize('remoteFileDialog.localFolderFallback', 'Open Local Folder');
67+
const openFileFolderString = nls.localize('remoteFileDialog.localFileFolderFallback', 'Open Local File or Folder');
68+
let tooltip = options.canSelectFiles ? (options.canSelectFolders ? openFileFolderString : openFileString) : openFolderString;
69+
this.fallbackPickerButton = { iconPath: this.getDialogIcons('folder'), tooltip };
6470
const remoteOptions: IOpenDialogOptions = objects.deepClone(options);
6571
remoteOptions.defaultUri = defaultUri;
6672
return this.pickResource(remoteOptions).then(async fileFolderUri => {
@@ -80,6 +86,7 @@ export class RemoteFileDialog {
8086
this.notificationService.info(nls.localize('remoteFileDialog.notConnectedToRemote', 'File system provider for {0} is not available.', defaultUri.toString()));
8187
return Promise.resolve(undefined);
8288
}
89+
this.fallbackPickerButton = { iconPath: this.getDialogIcons('folder'), tooltip: nls.localize('remoteFileDialog.localSaveFallback', 'Save Local File') };
8390
const remoteOptions: IOpenDialogOptions = objects.deepClone(options);
8491
remoteOptions.defaultUri = resources.dirname(defaultUri);
8592
remoteOptions.canSelectFolders = true;
@@ -111,7 +118,9 @@ export class RemoteFileDialog {
111118
this.currentFolder = homedir;
112119

113120
if (options.availableFileSystems && options.availableFileSystems.length > 1) {
114-
this.filePickBox.buttons = [this.fallbackPickerButton];
121+
this.filePickBox.buttons = [this.fallbackPickerButton, this.acceptButton];
122+
} else {
123+
this.filePickBox.buttons = [this.acceptButton];
115124
}
116125
this.filePickBox.onDidTriggerButton(button => {
117126
if (button === this.fallbackPickerButton) {
@@ -126,8 +135,17 @@ export class RemoteFileDialog {
126135
resolve(result ? result[0] : undefined);
127136
});
128137
}
138+
this.filePickBox.hide();
139+
} else { // accept button
140+
const resolveValue = this.remoteUriFrom(this.filePickBox.value);
141+
this.validate(resolveValue).then(validated => {
142+
if (validated) {
143+
isResolved = true;
144+
resolve(resolveValue);
145+
this.filePickBox.hide();
146+
}
147+
});
129148
}
130-
this.filePickBox.hide();
131149
});
132150

133151
this.filePickBox.title = options.title;
@@ -141,7 +159,9 @@ export class RemoteFileDialog {
141159
isAcceptHandled = true;
142160
this.onDidAccept().then(resolveValue => {
143161
if (resolveValue) {
162+
isResolved = true;
144163
resolve(resolveValue);
164+
this.filePickBox.hide();
145165
}
146166
});
147167
});
@@ -150,11 +170,16 @@ export class RemoteFileDialog {
150170
});
151171

152172
this.filePickBox.onDidChangeValue(value => {
153-
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
154-
const valueUri = this.remoteUriFrom(trimmedPickBoxValue);
155-
if (!resources.isEqual(this.currentFolder, valueUri)) {
156-
this.tryUpdateItems(value, valueUri);
173+
if (value !== this.userValue) {
174+
const trimmedPickBoxValue = ((this.filePickBox.value.length > 1) && this.endsWithSlash(this.filePickBox.value)) ? this.filePickBox.value.substr(0, this.filePickBox.value.length - 1) : this.filePickBox.value;
175+
const valueUri = this.remoteUriFrom(trimmedPickBoxValue);
176+
if (!resources.isEqual(this.currentFolder, valueUri)) {
177+
this.tryUpdateItems(value, valueUri);
178+
}
157179
this.setActiveItems(value);
180+
this.userValue = value;
181+
} else {
182+
this.filePickBox.activeItems = [];
158183
}
159184
});
160185
this.filePickBox.onDidHide(() => {
@@ -166,6 +191,7 @@ export class RemoteFileDialog {
166191

167192
this.filePickBox.show();
168193
this.updateItems(homedir, trailing);
194+
this.userValue = this.filePickBox.value;
169195
});
170196
}
171197

@@ -205,7 +231,7 @@ export class RemoteFileDialog {
205231
}
206232

207233
if (resolveValue) {
208-
if (this.validate(resolveValue)) {
234+
if (await this.validate(resolveValue)) {
209235
return Promise.resolve(resolveValue);
210236
}
211237
} else if (navigateValue) {
@@ -240,34 +266,38 @@ export class RemoteFileDialog {
240266
}
241267

242268
private setActiveItems(value: string) {
243-
const inputBasename = resources.basename(this.remoteUriFrom(value));
244-
let hasMatch = false;
245-
for (let i = 0; i < this.filePickBox.items.length; i++) {
246-
const item = <FileQuickPickItem>this.filePickBox.items[i];
247-
const itemBasename = resources.basename(item.uri);
248-
if ((itemBasename.length >= inputBasename.length) && (itemBasename.substr(0, inputBasename.length) === inputBasename)) {
249-
this.filePickBox.activeItems = [item];
250-
hasMatch = true;
251-
break;
269+
if (!this.userValue || (value !== this.userValue.substring(0, value.length))) {
270+
const inputBasename = resources.basename(this.remoteUriFrom(value));
271+
let hasMatch = false;
272+
for (let i = 0; i < this.filePickBox.items.length; i++) {
273+
const item = <FileQuickPickItem>this.filePickBox.items[i];
274+
const itemBasename = resources.basename(item.uri);
275+
if ((itemBasename.length >= inputBasename.length) && (itemBasename.substr(0, inputBasename.length) === inputBasename)) {
276+
this.filePickBox.activeItems = [item];
277+
this.filePickBox.value = this.filePickBox.value + itemBasename.substr(inputBasename.length);
278+
this.filePickBox.valueSelection = [value.length, this.filePickBox.value.length];
279+
hasMatch = true;
280+
break;
281+
}
282+
}
283+
if (!hasMatch) {
284+
this.filePickBox.activeItems = [];
252285
}
253-
}
254-
if (!hasMatch) {
255-
this.filePickBox.activeItems = [];
256286
}
257287
}
258288

259289
private async validate(uri: URI): Promise<boolean> {
260290
let stat: IFileStat | undefined;
261291
let statDirname: IFileStat | undefined;
262292
try {
263-
stat = await this.remoteFileService.resolveFile(uri);
264293
statDirname = await this.remoteFileService.resolveFile(resources.dirname(uri));
294+
stat = await this.remoteFileService.resolveFile(uri);
265295
} catch (e) {
266296
// do nothing
267297
}
268298

269299
if (this.requiresTrailing) { // save
270-
if (statDirname.isDirectory) {
300+
if (stat && stat.isDirectory) {
271301
// Can't do this
272302
return Promise.resolve(false);
273303
} else if (stat) {
@@ -276,6 +306,9 @@ export class RemoteFileDialog {
276306
} else if (!this.isValidBaseName(resources.basename(uri))) {
277307
// Filename not allowed
278308
return Promise.resolve(false);
309+
} else if (!statDirname || !statDirname.isDirectory) {
310+
// Folder to save in doesn't exist
311+
return Promise.resolve(false);
279312
}
280313
} else { // open
281314
if (!stat) {
@@ -395,10 +428,10 @@ export class RemoteFileDialog {
395428
}
396429
}
397430

398-
private getAlternateDialogIcons(): { light: URI, dark: URI } {
431+
private getDialogIcons(name: string): { light: URI, dark: URI } {
399432
return {
400-
dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/media/dark/Folder.svg`)),
401-
light: URI.parse(require.toUrl(`vs/workbench/services/dialogs/media/light/Folder_inverse.svg`))
433+
dark: URI.parse(require.toUrl(`vs/workbench/services/dialogs/electron-browser/media/dark/${name}.svg`)),
434+
light: URI.parse(require.toUrl(`vs/workbench/services/dialogs/electron-browser/media/light/${name}.svg`))
402435
};
403436
}
404437
}

0 commit comments

Comments
 (0)