Skip to content

Commit dc25752

Browse files
author
Benjamin Pasero
committed
text files - centralize file path suggestion for picker
1 parent f5a52f8 commit dc25752

3 files changed

Lines changed: 39 additions & 44 deletions

File tree

src/vs/workbench/common/editor/untitledTextEditorInput.ts

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,7 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { URI } from 'vs/base/common/uri';
7-
import { suggestFilename } from 'vs/base/common/mime';
87
import { createMemoizer } from 'vs/base/common/decorators';
9-
import { PLAINTEXT_MODE_ID } from 'vs/editor/common/modes/modesRegistry';
108
import { basenameOrAuthority, dirname } from 'vs/base/common/resources';
119
import { IEncodingSupport, EncodingMode, Verbosity, IModeSupport, TextEditorInput, GroupIdentifier, IRevertOptions } from 'vs/workbench/common/editor';
1210
import { UntitledTextEditorModel } from 'vs/workbench/common/editor/untitledTextEditorModel';
@@ -178,19 +176,6 @@ export class UntitledTextEditorInput extends TextEditorInput implements IEncodin
178176
return true;
179177
}
180178

181-
suggestFileName(): string {
182-
if (!this.hasAssociatedFilePath) {
183-
if (this.cachedModel) {
184-
const mode = this.cachedModel.getMode();
185-
if (mode !== PLAINTEXT_MODE_ID) { // do not suggest when the mode ID is simple plain text
186-
return suggestFilename(mode, this.getName());
187-
}
188-
}
189-
}
190-
191-
return this.getName();
192-
}
193-
194179
getEncoding(): string | undefined {
195180
if (this.cachedModel) {
196181
return this.cachedModel.getEncoding();

src/vs/workbench/services/textfile/browser/textFileService.ts

Lines changed: 39 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import { ITextModelService, IResolvedTextEditorModel } from 'vs/editor/common/se
3333
import { BaseTextEditorModel } from 'vs/workbench/common/editor/textEditorModel';
3434
import { ICodeEditorService } from 'vs/editor/browser/services/codeEditorService';
3535
import { 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

src/vs/workbench/test/common/editor/untitledTextEditor.test.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -168,14 +168,6 @@ suite('Workbench untitled text editors', () => {
168168
input.dispose();
169169
});
170170

171-
test('suggest name', function () {
172-
const service = accessor.untitledTextEditorService;
173-
const input = service.create();
174-
175-
assert.ok(input.suggestFileName().length > 0);
176-
input.dispose();
177-
});
178-
179171
test('associated path remains dirty when content gets empty', async () => {
180172
const service = accessor.untitledTextEditorService;
181173
const file = URI.file(join('C:\\', '/foo/file.txt'));

0 commit comments

Comments
 (0)