Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 6c597e7

Browse files
authored
Make SaveAs work for untitled files (#1305)
* Add saveas support for untitled files * Improve comment about not saving to non-file URI schemes * Improve logging in extension commands * Improve logging code * Address PR feedback * Add helpful messages * Add logging and save failure warnings
1 parent c1b643e commit 6c597e7

File tree

3 files changed

+120
-34
lines changed

3 files changed

+120
-34
lines changed

‎src/features/ExtensionCommands.ts‎

Lines changed: 117 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import * as path from "path";
88
import * as vscode from "vscode";
99
import { LanguageClient, NotificationType, Position, Range, RequestType } from "vscode-languageclient";
1010
import { IFeature } from "../feature";
11+
import { Logger } from "../logging";
1112

1213
export interface IExtensionCommand {
1314
name: string;
@@ -172,10 +173,11 @@ export class ExtensionCommandsFeature implements IFeature {
172173
private languageClient: LanguageClient;
173174
private extensionCommands: IExtensionCommand[] = [];
174175

175-
constructor() {
176+
constructor(privatelog: Logger) {
176177
this.command = vscode.commands.registerCommand("PowerShell.ShowAdditionalCommands", () => {
177178
if (this.languageClient === undefined) {
178-
// TODO: Log error message
179+
this.log.writeAndShowError(`<${ExtensionCommandsFeature.name}>: ` +
180+
"Unable to instantiate; language client undefined.");
179181
return;
180182
}
181183

@@ -388,44 +390,127 @@ export class ExtensionCommandsFeature implements IFeature {
388390
return promise;
389391
}
390392

393+
/**
394+
* Save a file, possibly to a new path. If the save is not possible, return a completed response
395+
* @param saveFileDetails the object detailing the path of the file to save and optionally its new path to save to
396+
*/
391397
private async saveFile(saveFileDetails: ISaveFileDetails): Promise<EditorOperationResponse> {
392-
393-
// If the file to save can't be found, just complete the request
394-
if (!this.findTextDocument(this.normalizeFilePath(saveFileDetails.filePath))) {
395-
return EditorOperationResponse.Completed;
398+
// Try to interpret the filepath as a URI, defaulting to "file://" if we don't succeed
399+
let currentFileUri: vscode.Uri;
400+
if (saveFileDetails.filePath.startsWith("untitled") || saveFileDetails.filePath.startsWith("file")) {
401+
currentFileUri = vscode.Uri.parse(saveFileDetails.filePath);
402+
} else {
403+
currentFileUri = vscode.Uri.file(saveFileDetails.filePath);
396404
}
397405

398-
// If no newFile is given, just save the current file
399-
if (!saveFileDetails.newPath) {
400-
const doc = await vscode.workspace.openTextDocument(saveFileDetails.filePath);
401-
if (doc.isDirty) {
402-
await doc.save();
403-
}
404-
405-
return EditorOperationResponse.Completed;
406+
let newFileAbsolutePath: string;
407+
switch (currentFileUri.scheme) {
408+
case "file":
409+
// If the file to save can't be found, just complete the request
410+
if (!this.findTextDocument(this.normalizeFilePath(currentFileUri.fsPath))) {
411+
this.log.writeAndShowError(`File to save not found: ${currentFileUri.fsPath}.`);
412+
return EditorOperationResponse.Completed;
413+
}
414+
415+
// If no newFile is given, just save the current file
416+
if (!saveFileDetails.newPath) {
417+
const doc = await vscode.workspace.openTextDocument(currentFileUri.fsPath);
418+
if (doc.isDirty) {
419+
await doc.save();
420+
}
421+
return EditorOperationResponse.Completed;
422+
}
423+
424+
// Make sure we have an absolute path
425+
if (path.isAbsolute(saveFileDetails.newPath)) {
426+
newFileAbsolutePath = saveFileDetails.newPath;
427+
} else {
428+
// If not, interpret the path as relative to the current file
429+
newFileAbsolutePath = path.join(path.dirname(currentFileUri.fsPath), saveFileDetails.newPath);
430+
}
431+
break;
432+
433+
case "untitled":
434+
// We need a new name to save an untitled file
435+
if (!saveFileDetails.newPath) {
436+
// TODO: Create a class handle vscode warnings and errors so we can warn easily
437+
// without logging
438+
this.log.writeAndShowWarning(
439+
"Cannot save untitled file. Try SaveAs(\"path/to/file.ps1\") instead.");
440+
return EditorOperationResponse.Completed;
441+
}
442+
443+
// Make sure we have an absolute path
444+
if (path.isAbsolute(saveFileDetails.newPath)) {
445+
newFileAbsolutePath = saveFileDetails.newPath;
446+
} else {
447+
// In fresh contexts, workspaceFolders is not defined...
448+
if (!vscode.workspace.workspaceFolders || vscode.workspace.workspaceFolders.length === 0) {
449+
this.log.writeAndShowWarning("Cannot save file to relative path: no workspaces are open. " +
450+
"Try saving to an absolute path, or open a workspace.");
451+
return EditorOperationResponse.Completed;
452+
}
453+
454+
// If not, interpret the path as relative to the workspace root
455+
const workspaceRootUri = vscode.workspace.workspaceFolders[0].uri;
456+
// We don't support saving to a non-file URI-schemed workspace
457+
if (workspaceRootUri.scheme !== "file") {
458+
this.log.writeAndShowWarning(
459+
"Cannot save untitled file to a relative path in an untitled workspace. " +
460+
"Try saving to an absolute path or opening a workspace folder.");
461+
return EditorOperationResponse.Completed;
462+
}
463+
newFileAbsolutePath = path.join(workspaceRootUri.fsPath, saveFileDetails.newPath);
464+
}
465+
break;
466+
467+
default:
468+
// Other URI schemes are not supported
469+
const msg = JSON.stringify(saveFileDetails);
470+
this.log.writeVerbose(
471+
`<${ExtensionCommandsFeature.name}>: Saving a document with scheme '${currentFileUri.scheme}' ` +
472+
`is currently unsupported. Message: '${msg}'`);
473+
return EditorOperationResponse.Completed;
406474
}
407475

408-
// Otherwise we want to save as a new file
409-
410-
// First turn the path we were given into an absolute path
411-
// Relative paths are interpreted as relative to the original file
412-
const newFileAbsolutePath = path.isAbsolute(saveFileDetails.newPath) ?
413-
saveFileDetails.newPath :
414-
path.resolve(path.dirname(saveFileDetails.filePath), saveFileDetails.newPath);
415-
416-
// Retrieve the text out of the current document
417-
const oldDocument = await vscode.workspace.openTextDocument(saveFileDetails.filePath);
476+
await this.saveDocumentContentToAbsolutePath(currentFileUri, newFileAbsolutePath);
477+
return EditorOperationResponse.Completed;
418478

419-
// Write it to the new document path
420-
fs.writeFileSync(newFileAbsolutePath, oldDocument.getText());
479+
}
421480

422-
// Finally open the new document
423-
const newFileUri = vscode.Uri.file(newFileAbsolutePath);
424-
const newFile = await vscode.workspace.openTextDocument(newFileUri);
425-
vscode.window.showTextDocument(newFile, { preview: true });
481+
/**
482+
* Take a document available to vscode at the given URI and save it to the given absolute path
483+
* @param documentUri the URI of the vscode document to save
484+
* @param destinationAbsolutePath the absolute path to save the document contents to
485+
*/
486+
private async saveDocumentContentToAbsolutePath(
487+
documentUri: vscode.Uri,
488+
destinationAbsolutePath: string): Promise<void> {
489+
// Retrieve the text out of the current document
490+
const oldDocument = await vscode.workspace.openTextDocument(documentUri);
491+
492+
// Write it to the new document path
493+
try {
494+
// TODO: Change this to be asyncronous
495+
await new Promise<void>((resolve, reject) => {
496+
fs.writeFile(destinationAbsolutePath, oldDocument.getText(), (err) => {
497+
if (err) {
498+
return reject(err);
499+
}
500+
return resolve();
501+
});
502+
});
503+
} catch (e) {
504+
this.log.writeAndShowWarning(`<${ExtensionCommandsFeature.name}>: ` +
505+
`Unable to save file to path '${destinationAbsolutePath}': ${e}`);
506+
return;
507+
}
426508

427-
return EditorOperationResponse.Completed;
428-
}
509+
// Finally open the new document
510+
const newFileUri = vscode.Uri.file(destinationAbsolutePath);
511+
const newFile = await vscode.workspace.openTextDocument(newFileUri);
512+
vscode.window.showTextDocument(newFile, { preview: true });
513+
}
429514

430515
private normalizeFilePath(filePath: string): string {
431516
const platform = os.platform();

‎src/features/HelpCompletion.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ export class HelpCompletionFeature implements IFeature {
5656

5757
public onEvent(changeEvent: TextDocumentChangeEvent): void {
5858
if (!(changeEvent && changeEvent.contentChanges)) {
59-
this.log.write(`Bad TextDocumentChangeEvent message: ${JSON.stringify(changeEvent)}`);
59+
this.log.writeWarning(`<${HelpCompletionFeature.name}>: ` +
60+
`Bad TextDocumentChangeEvent message: ${JSON.stringify(changeEvent)}`);
6061
return;
6162
}
6263

‎src/main.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ export function activate(context: vscode.ExtensionContext): void {
121121
new ShowHelpFeature(),
122122
new FindModuleFeature(),
123123
new PesterTestsFeature(sessionManager),
124-
new ExtensionCommandsFeature(),
124+
new ExtensionCommandsFeature(logger),
125125
new SelectPSSARulesFeature(),
126126
new CodeActionsFeature(),
127127
new NewFileOrProjectFeature(),

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /