# encoding: ascii
# api: powershell
# title: Find-Editor
# description: A tool to find a text editor
# version: 0.1
# type: function
# author: Joel Bennett
# license: CC0
# function: Find-Editor
# x-poshcode-id: 5529
# x-archived: 2014-12-24T21:41:32
# x-published: 2014-10-21T01:32:00
#
#

        function Find-Editor {
            #.Synopsis
            #   Find a simple code editor
            #.Description
            #   Tries to find a text editor based on the PSEditor preference variable, the EDITOR environment variable, or your configuration for git.  As a fallback it searches for Sublime, Notepad++, or Atom, and finally falls back to Notepad. 
            #   
            #   I have deliberately excluded PowerShell_ISE because it is a single-instance app which doesn't support "wait" if it's already running.  That is, if PowerShell_ISE is already running, issuing a command like this will return immediately:
            #   
            #   Start-Process PowerShell_ISE $Profile -Wait
            [CmdletBinding()]
            param
            (
                # Specifies a code editor. If the editor is in the Path environment variable (Get-Command <editor>), you can enter just the editor name. Otherwise, enter the path to the executable file for the editor.
                # Defaults to the value of $PSEditor.Command or $PSEditor or Env:Editor if any of them are set.
                [Parameter(Position=1)] 
                [System.String]
                $Editor = $(if($global:PSEditor.Command){ $global:PSEditor.Command } else { $global:PSEditor } ),

                # Specifies commandline parameters for the editor. 
                # Edit-Function.ps1 passes these editor-specific parameters to the editor you select. 
                # For example, sublime uses -n -w to trigger a mode where closing the *tab* will return
                [Parameter(Position=2)]
                [System.String]
                $Parameters = $global:PSEditor.Parameters
            )
            begin {
                function Split-Command {
                    # The normal (unix) "Editor" environment variable can include parameters
                    # so it can be executed from command by just appending the file name.
                    # However, PowerShell can't deal with that, so:
                    param(
                        [Parameter(Mandatory=$true)]
                        [string]$Command
                    )
                    $Parts = @($Command -Split " ")

                    for($count=$Parts.Length; $count -gt 1; $count--) {
                        $Editor = ($Parts[0..$count] -join " ").Trim("'",'"')
                        if((Test-Path $Editor) -and (Get-Command $Editor -ErrorAction SilentlyContinue)) { 
                            $Editor
                            $Parts[$($Count+1)..$($Parts.Length)] -join " "
                            break
                        }
                    }
                }
            }

            end {
                do { # This is the GOTO hack: use break to skip to the end once we find it:
                    # In this test, we let the Get-Command error leak out on purpose
                    if($Editor -and (Get-Command $Editor)) { break }

                    if ($Editor -and !(Get-Command $Editor -ErrorAction SilentlyContinue))
                    {
                        Write-Verbose "Editor is not a valid command, split it:"
                        $Editor, $Parameters = Split-Command $Editor
                        if($Editor) { break }
                    }

                    if (Test-Path Env:Editor)
                    {
                        Write-Verbose "Editor was not passed in, trying Env:Editor"
                        $Editor, $Parameters = Split-Command $Env:Editor
                        if($Editor) { break }
                    }

                    # If no editor is specified, try looking in git config
                    if (Get-Command Git -ErrorAction SilentlyContinue)
                    {
                        Write-Verbose "PSEditor and Env:Editor not found, searching git config"
                        $Editor, $Parameters = Split-Command (git config core.editor)
                        if($Editor) { break }
                    }

                    # Try a few common ones that might be in the path
                    Write-Verbose "Editor not found, trying some others"
                    if($Editor = Get-Command "subl.exe", "sublime_text.exe" | Select-Object -Expand Path -first 1)
                    {
                        $Parameters = "-n -w"
                        break
                    }
                    if($Editor = Get-Command "notepad++.exe" | Select-Object -Expand Path -first 1)
                    {
                        $Parameters = "-multiInst"
                        break
                    }
                    if($Editor = Get-Command "atom.exe" | Select-Object -Expand Path -first 1)
                    {
                        break
                    }

                    # Search the slow way for sublime
                    Write-Verbose "Editor still not found, getting desperate:"
                    if(($Editor = Get-Item "C:\Program Files\DevTools\Sublime Text ?\sublime_text.exe" -ErrorAction SilentlyContinue | Sort {$_.VersionInfo.FileVersion} -Descending | Select-Object -First 1) -or
                       ($Editor = Get-ChildItem C:\Program*\* -recurse -filter "sublime_text.exe" -ErrorAction SilentlyContinue | Select-Object -First 1))
                    {
                        $Parameters = "-n -w"
                        break
                    }

                    if(($Editor = Get-ChildItem "C:\Program Files\Notepad++\notepad++.exe" -recurse -filter "notepad++.exe" -ErrorAction SilentlyContinue | Select-Object -First 1) -or
                       ($Editor = Get-ChildItem C:\Program*\* -recurse -filter "notepad++.exe" -ErrorAction SilentlyContinue | Select-Object -First 1))
                    {
                        $Parameters = "-multiInst"
                        break
                    }

                    # Settling for Notepad
                    Write-Verbose "Editor not found, settling for notepad"
                    $Editor = "notepad"

                    if(!$Editor -or !(Get-Command $Editor -ErrorAction SilentlyContinue -ErrorVariable NotFound)) { 
                        if($NotFound) { $PSCmdlet.ThrowTerminatingError( $NotFound[0] ) }
                        else { 
                            throw "Could not find an editor (not even notepad!)"
                        }
                    }
                } while($false)

                $PSEditor = New-Object PSObject -Property @{
                        Command = "$Editor"
                        Parameters = "$Parameters"
                } | Add-Member ScriptMethod ToString -Value { "'" + $this.Command + "' " + $this.Parameters } -Force -PassThru

                # There are several reasons we might need to update the editor variable
                if($PSBoundParameters.ContainsKey("Editor") -or 
                   $PSBoundParameters.ContainsKey("Parameters") -or 
                   !(Test-Path variable:global:PSeditor) -or 
                   ($PSEditor.Command -ne $Editor))
                {
                    # Store it pre-parsed and everything in the current session:
                    Write-Verbose "Setting global preference variable for Editor: PSEditor"
                    $global:PSEditor = $PSEditor

                    # Store it stickily in the environment variable
                    if(![Environment]::GetEnvironmentVariable("Editor", "User")) {
                        Write-Verbose "Setting user environment variable: Editor"
                        [Environment]::SetEnvironmentVariable("Editor", "$PSEditor", "User")
                    }
                }
                return $PSEditor
            }
        }
