@@ -33,6 +33,7 @@ import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/se
3333import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel' ;
3434import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService' ;
3535import { coalesce } from 'vs/base/common/arrays' ;
36+ import { suggestFilename } from 'vs/base/common/mime' ;
3637
3738/**
3839 * The workbench file service implementation implements the raw file service spec and adds additional methods on top.
@@ -306,12 +307,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
306307
307308 // Untitled with associated file path don't need to prompt
308309 if ( model . hasAssociatedFilePath ) {
309- targetUri = toLocalResource ( resource , this . environmentService . configuration . remoteAuthority ) ;
310+ targetUri = this . suggestSavePath ( resource ) ;
310311 }
311312
312313 // Otherwise ask user
313314 else {
314- targetUri = await this . promptForPath ( resource , this . suggestUntitledFilePath ( resource ) ) ;
315+ targetUri = await this . promptForPath ( resource , options ?. availableFileSystems ) ;
315316 }
316317
317318 // Save as if target provided
@@ -336,12 +337,12 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
336337 return undefined ;
337338 }
338339
339- protected async promptForPath ( resource : URI , defaultUri : URI , availableFileSystems ?: string [ ] ) : Promise < URI | undefined > {
340+ protected async promptForPath ( resource : URI , availableFileSystems ?: string [ ] ) : Promise < URI | undefined > {
340341
341342 // Help user to find a name for the file by opening it first
342343 await this . editorService . openEditor ( { resource, options : { revealIfOpened : true , preserveFocus : true } } ) ;
343344
344- return this . fileDialogService . pickFileToSave ( defaultUri , availableFileSystems ) ;
345+ return this . fileDialogService . pickFileToSave ( this . suggestSavePath ( resource ) , availableFileSystems ) ;
345346 }
346347
347348 private getFileModels ( resources ?: URI [ ] ) : ITextFileEditorModel [ ] {
@@ -360,12 +361,7 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
360361
361362 // Get to target resource
362363 if ( ! target ) {
363- let dialogPath = source ;
364- if ( source . scheme === Schemas . untitled ) {
365- dialogPath = this . suggestUntitledFilePath ( source ) ;
366- }
367-
368- target = await this . promptForPath ( source , dialogPath , options ?. availableFileSystems ) ;
364+ target = await this . promptForPath ( source , options ?. availableFileSystems ) ;
369365 }
370366
371367 if ( ! target ) {
@@ -544,27 +540,49 @@ export abstract class AbstractTextFileService extends Disposable implements ITex
544540 return ( await this . dialogService . confirm ( confirm ) ) . confirmed ;
545541 }
546542
547- private suggestUntitledFilePath ( untitledResource : URI ) : URI {
543+ private suggestSavePath ( resource : URI ) : URI {
544+
545+ // Just take the resource as is if the file service can handle it
546+ if ( this . fileService . canHandleResource ( resource ) ) {
547+ return resource ;
548+ }
549+
548550 const remoteAuthority = this . environmentService . configuration . remoteAuthority ;
549- const targetScheme = remoteAuthority ? Schemas . vscodeRemote : Schemas . file ;
550551
551- // Untitled with associated file path
552- const model = this . untitledTextEditorService . get ( untitledResource ) ;
553- if ( model ?. hasAssociatedFilePath ) {
554- return untitledResource . with ( { scheme : targetScheme } ) ;
552+ // Otherwise try to suggest a path that can be saved
553+ let suggestedFilename : string | undefined = undefined ;
554+ if ( resource . scheme === Schemas . untitled ) {
555+ const model = this . untitledTextEditorService . get ( resource ) ;
556+ if ( model ) {
557+
558+ // Untitled with associated file path
559+ if ( model . hasAssociatedFilePath ) {
560+ return toLocalResource ( resource , remoteAuthority ) ;
561+ }
562+
563+ // Untitled without associated file path
564+ const mode = model . getMode ( ) ;
565+ if ( mode !== PLAINTEXT_MODE_ID ) { // do not suggest when the mode ID is simple plain text
566+ suggestedFilename = suggestFilename ( mode , model . getName ( ) ) ;
567+ } else {
568+ suggestedFilename = model . getName ( ) ;
569+ }
570+ }
555571 }
556572
557- // Untitled without associated file path
558- const untitledFileName = model ?. suggestFileName ( ) ?? basename ( untitledResource ) ;
573+ // Fallback to basename of resource
574+ if ( ! suggestedFilename ) {
575+ suggestedFilename = basename ( resource ) ;
576+ }
559577
560578 // Try to place where last active file was if any
561- const defaultFilePath = this . fileDialogService . defaultFilePath ( targetScheme ) ;
579+ const defaultFilePath = this . fileDialogService . defaultFilePath ( ) ;
562580 if ( defaultFilePath ) {
563- return joinPath ( defaultFilePath , untitledFileName ) ;
581+ return joinPath ( defaultFilePath , suggestedFilename ) ;
564582 }
565583
566584 // Finally fallback to suggest just the file name
567- return untitledResource . with ( { scheme : targetScheme , path : untitledFileName } ) ;
585+ return toLocalResource ( resource . with ( { path : suggestedFilename } ) , remoteAuthority ) ;
568586 }
569587
570588 //#endregion
0 commit comments