@@ -31,14 +31,15 @@ const INVALID_FILE_CHARS = isWindows ? /[\\/:\*\?"<>\|]/g : /[\\/]/g;
3131const WINDOWS_FORBIDDEN_NAMES = / ^ ( c o n | p r n | a u x | c l o c k \$ | n u l | l p t [ 0 - 9 ] | c o m [ 0 - 9 ] ) $ / i;
3232
3333export 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