Skip to content

Add New-EditorFile #622

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

Merged
merged 4 commits into from
Feb 20, 2018
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ FunctionsToExport = @('Register-EditorCommand',
'Out-CurrentFile',
'Join-ScriptExtent',
'Test-ScriptExtent',
'Open-EditorFile')
'Open-EditorFile',
'New-EditorFile')

# Cmdlets to export from this module, for best performance, do not use wildcards and do not delete the entry, use an empty array if there are no cmdlets to export.
CmdletsToExport = @()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,91 @@ function Unregister-EditorCommand {
}
}

<#
.SYNOPSIS
Creates new files and opens them in your editor window
.DESCRIPTION
Creates new files and opens them in your editor window
.EXAMPLE
PS > New-EditorFile './foo.ps1'
Creates and opens a new foo.ps1 in your editor
.EXAMPLE
PS > Get-Process | New-EditorFile proc.txt
Creates and opens a new foo.ps1 in your editor with the contents of the call to Get-Process
.EXAMPLE
PS > Get-Process | New-EditorFile proc.txt -Force
Creates and opens a new foo.ps1 in your editor with the contents of the call to Get-Process. Overwrites the file if it already exists
.INPUTS
Path
an array of files you want to open in your editor
Value
The content you want in the new files
Force
Overwrites a file if it exists
#>
function New-EditorFile {
[CmdletBinding()]
param(
[Parameter(Mandatory=$true)]
[String[]]
$Path,

[Parameter(ValueFromPipeline=$true)]
$Value,

[Parameter()]
[switch]
$Force
)

begin {
$container = @()
}

process {
$container += $Value
}

end {
foreach ($fileName in $Path)
{
if (-not (Test-Path $fileName) -or $Force) {
$container > $fileName
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd really like to see this edit the file through $psEditor instead of directly.

$psEditor.Workspace.OpenFile($fileName)
} else {
$PSCmdlet.WriteError( (
New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList @(
[System.Exception]'File already exists.'
Copy link
Collaborator

Choose a reason for hiding this comment

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

Can we model this error record after New-Item?

New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList @(
    [System.IO.IOException]"The file '$fileName' already exists.",
    'NewEditorFileIOError',
    [System.Management.Automation.ErrorCategory]::WriteError,
    $fileName)

$Null
[System.Management.Automation.ErrorCategory]::ResourceExists
$fileName ) ) )
}
}
}
}

function Open-EditorFile {
param([Parameter(Mandatory=$true)]$FilePaths)
[CmdletBinding()]
param(
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[ValidateNotNullOrEmpty()]
$Path
Copy link
Contributor

Choose a reason for hiding this comment

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

As long as this parameter supports this scenario Get-ChildItem *.ps1 -r -file | Open-EditorFile, I'm good.

Copy link
Member Author

Choose a reason for hiding this comment

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

So @rkeithhill ... as it turns out, this will proceed to open the file temporarily (with the italicized file name)... so what ends up happening is, it opens the file temporarily and then the next file opens and replaces the file temporarily.

I'm not sure how to durably open files - if that's possible. I'll have to dig around.

@daviwil any thoughts?

Copy link
Contributor

Choose a reason for hiding this comment

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

Ah, yes. That's why I double-click the editor window tab. That converts it from a temp (resuable) window to a permanent one. Not sure how you do that programmatically though.

Copy link
Member Author

Choose a reason for hiding this comment

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

Exactly. Maybe if @SeeminglyScience or @daviwil know? Otherwise, can I convince you to call this out of scope 😄

Copy link
Contributor

@rkeithhill rkeithhill Feb 13, 2018

Choose a reason for hiding this comment

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

You can use the vscode.window.showTextDocument() overload that takes a TextDocumentShowOptions object. One of the fields of that object is preview?. That should allow you to create each window in a "non-preview" window.

https://code.visualstudio.com/docs/extensionAPI/vscode-api#_a-nametextdocumentshowoptionsaspan-classcodeitem-id239textdocumentshowoptionsspan

Copy link
Contributor

Choose a reason for hiding this comment

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

That would be my preference. Later we could update the openFile() method to take an optional "preview" Boolean parameter.

Copy link
Member Author

Choose a reason for hiding this comment

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

Later we could update the openFile() method to take an optional "preview" Boolean parameter.

What if I already have this working? Is it worth adding it now or saving it for when someone actually asks for that?

Copy link
Member Author

Choose a reason for hiding this comment

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

I'll just push and you can judge whether we should keep it in or not

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to have that option, so yeah, if it's working - go for it.

Copy link
Member Author

Choose a reason for hiding this comment

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

@rkeithhill pushed that. Here's the vscode-powershell PR as well:
PowerShell/vscode-powershell#1197

)

begin {
$Paths = @()
}

Get-ChildItem $FilePaths -File | ForEach-Object {
$psEditor.Workspace.OpenFile($_.FullName)
process {
$Paths += $Path
}

end {
Get-ChildItem $Paths -File | ForEach-Object {
$psEditor.Workspace.OpenFile($_.FullName)
}
}
}
Set-Alias psedit Open-EditorFile -Scope Global

Export-ModuleMember -Function Open-EditorFile
Export-ModuleMember -Function Open-EditorFile,New-EditorFile
173 changes: 136 additions & 37 deletions src/PowerShellEditorServices/Session/RemoteFileManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,55 +36,161 @@ public class RemoteFileManager

private const string RemoteSessionOpenFile = "PSESRemoteSessionOpenFile";

private const string PSEditFunctionScript = @"
param (
[Parameter(Mandatory=$true)] [String[]] $FileNames
)
private const string PSEditModule = @"<#
.SYNOPSIS
Opens the specified files in your editor window
.DESCRIPTION
Opens the specified files in your editor window
.EXAMPLE
PS > Open-EditorFile './foo.ps1'
Opens foo.ps1 in your editor
.EXAMPLE
PS > gci ./myDir | Open-EditorFile
Opens everything in 'myDir' in your editor
.INPUTS
Path
an array of files you want to open in your editor
#>
function Open-EditorFile {
param (
[Parameter(Mandatory=$true, ValueFromPipeline=$true)]
[String[]]
$Path
)

begin {
$Paths = @()
}

foreach ($fileName in $FileNames)
{
dir $fileName | where { ! $_.PSIsContainer } | foreach {
$filePathName = $_.FullName
process {
$Paths += $Path
}

# Get file contents
$params = @{ Path=$filePathName; Raw=$true }
if ($PSVersionTable.PSEdition -eq 'Core')
end {
foreach ($fileName in $Paths)
{
$params['AsByteStream']=$true
dir $fileName | where { ! $_.PSIsContainer } | foreach {
$filePathName = $_.FullName

# Get file contents
$params = @{ Path=$filePathName; Raw=$true }
if ($PSVersionTable.PSEdition -eq 'Core')
{
$params['AsByteStream']=$true
}
else
{
$params['Encoding']='Byte'
}

$contentBytes = Get-Content @params

# Notify client for file open.
New-Event -SourceIdentifier PSESRemoteSessionOpenFile -EventArguments @($filePathName, $contentBytes) > $null
}
}
else
}
}

<#
.SYNOPSIS
Creates new files and opens them in your editor window
.DESCRIPTION
Creates new files and opens them in your editor window
.EXAMPLE
PS > New-EditorFile './foo.ps1'
Creates and opens a new foo.ps1 in your editor
.EXAMPLE
PS > Get-Process | New-EditorFile proc.txt
Creates and opens a new foo.ps1 in your editor with the contents of the call to Get-Process
.EXAMPLE
PS > Get-Process | New-EditorFile proc.txt -Force
Creates and opens a new foo.ps1 in your editor with the contents of the call to Get-Process. Overwrites the file if it already exists
.INPUTS
Path
an array of files you want to open in your editor
Value
The content you want in the new files
Force
Overwrites a file if it exists
#>
function New-EditorFile {
[CmdletBinding()]
param (
[Parameter(Mandatory=$true)]
[String[]]
$Path,

[Parameter(ValueFromPipeline=$true)]
$Value,

[Parameter()]
[switch]
$Force
)

begin {
$container = @()
}

process {
$container += $Value
}

end {
foreach ($fileName in $Path)
{
$params['Encoding']='Byte'
}
if (-not (Test-Path $fileName) -or $Force) {
$container > $fileName

$contentBytes = Get-Content @params
# Get file contents
$params = @{ Path=$fileName; Raw=$true }
if ($PSVersionTable.PSEdition -eq 'Core')
{
$params['AsByteStream']=$true
}
else
{
$params['Encoding']='Byte'
}

# Notify client for file open.
New-Event -SourceIdentifier PSESRemoteSessionOpenFile -EventArguments @($filePathName, $contentBytes) > $null
$contentBytes = Get-Content @params

# Notify client for file open.
New-Event -SourceIdentifier PSESRemoteSessionOpenFile -EventArguments @($fileName, $contentBytes) > $null
} else {
$PSCmdlet.WriteError( (
New-Object -TypeName System.Management.Automation.ErrorRecord -ArgumentList @(
[System.Exception]'File already exists.'
$Null
[System.Management.Automation.ErrorCategory]::ResourceExists
$fileName ) ) )
}
}
}
}

Set-Alias psedit Open-EditorFile -Scope Global
Export-ModuleMember -Function Open-EditorFile, New-EditorFile
";

// This script is templated so that the '-Forward' parameter can be added
// to the script when in non-local sessions
private const string CreatePSEditFunctionScript = @"
param (
[string] $PSEditFunction
[string] $PSEditModule
)

Register-EngineEvent -SourceIdentifier PSESRemoteSessionOpenFile -Forward

if ((Test-Path -Path 'function:\global:Open-EditorFile') -eq $false)
{{
Set-Item -Path 'function:\global:Open-EditorFile' -Value $PSEditFunction
Set-Alias psedit Open-EditorFile -Scope Global
}}
$psedit = New-Module -ScriptBlock ([Scriptblock]::Create($PSEditModule)) -Name PSEdit
$psedit.ExportedFunctions.Keys | ForEach-Object {
Set-Item -Path function:\global:$_ -Value $asdf.ExportedFunctions[$_].Definition -Force
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

If we're going the module route, can we pipe this into Import-Module instead?

Copy link
Member Author

Choose a reason for hiding this comment

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

@SeeminglyScience I tried Import-Module initially but it did not work. None of the cmdlets would show up... Could I have been missing a particular flag? I tried -Force and -Scope.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@tylerl0706 try -Global. I thought you only needed that when importing from a different session state, but I guess it's scope based. TIL

";

private const string RemovePSEditFunctionScript = @"
if (Test-Path -Path 'function:\global:Open-EditorFile')
{
Remove-Item -Path 'function:\global:Open-EditorFile' -Force
Get-Command | Where-Object {$_.Source -eq 'PSEdit'} | ForEach-Object {
Remove-Item -Path function:\global:$($_.Name) -Force
Copy link
Collaborator

Choose a reason for hiding this comment

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

Same as above, Get-Module PSEdit | Remove-Module would be preferable

}

if (Test-Path -Path 'alias:\psedit')
Expand Down Expand Up @@ -488,17 +594,10 @@ private void RegisterPSEditFunction(RunspaceDetails runspaceDetails)
{
runspaceDetails.Runspace.Events.ReceivedEvents.PSEventReceived += HandlePSEventReceived;

var createScript =
string.Format(
CreatePSEditFunctionScript,
(runspaceDetails.Location == RunspaceLocation.Local &&
runspaceDetails.Context == RunspaceContext.Original)
? string.Empty : "-Forward");

PSCommand createCommand = new PSCommand();
createCommand
.AddScript(createScript)
.AddParameter("PSEditFunction", PSEditFunctionScript);
.AddScript(CreatePSEditFunctionScript)
.AddParameter("PSEditModule", PSEditModule);

if (runspaceDetails.Context == RunspaceContext.DebuggedRunspace)
{
Expand Down