Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add CurrentFile SaveAs() support #1261

Merged
merged 5 commits into from
Apr 18, 2018

Conversation

rjmholt
Copy link
Contributor

@rjmholt rjmholt commented Apr 6, 2018

In combination with PSES #650, this adds the ability to run $psEditor.GetEditorContext().CurrentFile.SaveAs("newFile").

Some things to note (that I'm looking for input on):

  • The SaveAs method supports both absolute and relative paths, relative being relative to the current file path.
  • I've been trying to make VSCode open the newly saved file in a a tab/window, but after trying quite a few different things, it seems to really want to close the window. Any ideas?
  • There are a few ways the saving can fail. My current solution is for PSES to handle that possibility with pre-validation, but maybe we should send a success/fail message back instead?

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 10, 2018

Yeah, getting rid of the nesting is tough because you're building context as you go (it's secretly a monad).

But I think you're quite right, we can reduce the complexity by loosening the sequencing requirements.

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 10, 2018

I think I tried using saveFileDetails.newPath but it opens a strange editor window which isn't really native. I had to pass it the new document handle to get it to work.

});
});
});
});
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

triggered

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Needs a nice syntactic sugar I think...

@TylerLeonhardt
Copy link
Member

TylerLeonhardt commented Apr 12, 2018

I'm considering posting on Stack Overflow on how one might saveAs using the vscode API. I feel like it's a reasonable question and people would have use of it. Thoughts?

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 12, 2018

Yeah I agree. Either there or on their GitHub page

@TylerLeonhardt
Copy link
Member

TylerLeonhardt commented Apr 12, 2018

Here it is: https://stackoverflow.com/questions/49787354/implementing-a-save-as-using-the-vscode-api

We can open an issue in a day or so if we don't get any bites.

@SeeminglyScience
Copy link
Collaborator

SeeminglyScience commented Apr 12, 2018

Any reason an async method won't work?

private async saveFile(saveFileDetails: ISaveFileDetails): Promise<EditorOperationResponse> {

    // If the file to save can't be found, just complete the request
    if (!this.findTextDocument(this.normalizeFilePath(saveFileDetails.filePath))) {
        return EditorOperationResponse.Completed;
    }

    // If no newFile is given, just save the current file
    if (!saveFileDetails.newPath) {
        const doc = await vscode.workspace.openTextDocument(saveFileDetails.filePath);
        if (doc.isDirty) {
            await doc.save();
        }
        return EditorOperationResponse.Completed;
    }

    // Otherwise we want to save as a new file

    // First turn the path we were given into an absolute path
    // Relative paths are interpreted as relative to the original file
    const newFileAbsolutePath = path.isAbsolute(saveFileDetails.newPath) ?
        saveFileDetails.newPath :
        path.resolve(path.dirname(saveFileDetails.filePath), saveFileDetails.newPath);

    // Create an "untitled-scheme" path so that VSCode will let us create a new file with a given path
    const newFileUri = vscode.Uri.parse("untitled:" + newFileAbsolutePath);

    // Now we need to copy the content of the current file,
    // then create a new file at the given path, insert the content,
    // save it and open the document
    const oldDoc = await vscode.workspace.openTextDocument(saveFileDetails.filePath);
    const newDoc = await vscode.workspace.openTextDocument(newFileUri);
    const editor = await vscode.window.showTextDocument(newDoc, 1, false);
    await editor.edit((editBuilder) => {
        editBuilder.insert(new vscode.Position(0, 0), oldDoc.getText());
    });
    await newDoc.save();
    return EditorOperationResponse.Completed;
}

Edit: Completely missed the part about it not working in general. Whoops :)

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 12, 2018

Wow, that's much better. I had no idea async was a thing in TypeScript...

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 12, 2018

Now the question remains, how do we bring the new document window into focus. (The async behaviour could change it, but I imagine it won't). Currently, the new window comes into focus, saves, and is closed again. Leaving us looking at the unsaved old window. But I'm imagining that when I save-as a file, I want to see the new saved document open in front of me... (Unless that seems wrong?)

@TylerLeonhardt
Copy link
Member

didn't realize we could use async/await here 😄 thanks for that @SeeminglyScience!

@TylerLeonhardt
Copy link
Member

I wonder if it's because you're saving an untitled file:

const newFileUri = vscode.Uri.parse("untitled:" + newFileAbsolutePath);

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 12, 2018

I've been trying various ways to "save-as" and then open that window.

Opening with a new "file://" URI after the save, I get the new file opened. But then the old one closes, which isn't what I imagine people wanting.

I'm mildly reluctant to bypass VSCode and just use fs like the SO answer suggests, but it might be much easier...

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 16, 2018

Ok, I've fixed up the code (resolved by pulling the text out of the unsaved buffer, saving it to the filesystem and the opening the new file...) and it now seems to do what I want. We should probably test it out a bit and validate the behaviour (and see if using different paths breaks it) before merging, but I think it's good to go.

@rkeithhill
Copy link
Contributor

I wonder if we should just ignore the package-lock.json file altogether? We've started doing this at work as this file kept getting changed by everyone's dev environment. We could nuke this by adding a .npmrc with this in it:

package-lock=false

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 16, 2018

Yeah, I had added it to the ignore on the basis that it was generated by the user's environment, but @tylerl0706 suggested we should keep it in as it represents some pre-resolved work for npm.

npm's pages state that it's intended to be tracked, but if it keeps changing in every commit because different machines use it differently, then I'd argue that it's noise in the commit and we should ignore it (and npm should work out what they're trying to achieve with this file...). But I don't have a strong opinion either way (as long as deleting package-lock.json doesn't get us into dependency hell or something).

Copy link
Member

@TylerLeonhardt TylerLeonhardt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async/await is beautiful and this LGTM! Just a couple questions that I'm curious about.

const oldDocText = oldDocument.getText();

// Write it to the new document path
fs.writeFileSync(newFileAbsolutePath, oldDocText);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick:

fs.writeFileSync(newFileAbsolutePath, oldDocument.getText());

deletes a line.

might be worth it but don't care that much.

// Finally open the new document
const newFileUri = vscode.Uri.file(newFileAbsolutePath);
const newFile = await vscode.workspace.openTextDocument(newFileUri);
vscode.window.showTextDocument(newFile, { preview: false });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you choose to open it not as a preview? Just curious. Not really leaning either way personally.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, didn't know what it was (just followed the response on SO figuring it prevented some behaviour). I feel like maybe it actually should be a preview...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm leaning on preview actually too 😄

@@ -382,24 +388,45 @@ export class ExtensionCommandsFeature implements IFeature {
return promise;
}

private saveFile(filePath: string): Thenable<EditorOperationResponse> {
private async saveFile(saveFileDetails: ISaveFileDetails): Promise<EditorOperationResponse> {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you have to switch this from a Thenable to a Promise to use async/await?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah -- as in @SeeminglyScience's snippet. Thenable doesn't type check.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good to know!

// Finally open the new document
const newFileUri = vscode.Uri.file(newFileAbsolutePath);
const newFile = await vscode.workspace.openTextDocument(newFileUri);
vscode.window.showTextDocument(newFile, { preview: true });
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

preview should be true by default. Were you seeing otherwise?

Copy link
Contributor Author

@rjmholt rjmholt Apr 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No no, and actually I originally just took it out. But I couldn't find a reference to what the default is in the documentation and decided that we may as well make the expected behaviour explicit.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good!

@brwilkinson
Copy link

I have tested out the SaveAs('d:\test.ps1')

This appears to work if the file is already saved as another file, however not if the file is Untitled?

E.g.
Path : untitled:Untitled-1
WorkspacePath : untitled:Untitled-1

Is someone able to test this and see if they have the same experience ?

@rjmholt
Copy link
Contributor Author

rjmholt commented Apr 27, 2018

You're quite right. I've opened an issue: #1303.

@rjmholt rjmholt deleted the filecontext-saveas branch December 11, 2019 21:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants