TreePlus.psm1

function Show-Tree {
    <#
.SYNOPSIS
    Displays a tree-like structure of folders and optionally files.
 
.DESCRIPTION
    Recursively lists folder structures using ASCII-style connectors, with optional emojis, file filters,
    extension-based color themes, and support for clipboard or Markdown output.
 
.PARAMETER Path
    The root path to start displaying the tree.
 
.PARAMETER Depth
    Maximum tree depth to display. Default is unlimited.
 
.PARAMETER ShowFiles
    Include files in the output tree.
 
.PARAMETER IncludeExtensions
    Only include files with these extensions (e.g. ".ps1", ".txt").
 
.PARAMETER ExcludeFolders
    Folder names to exclude from output.
 
.PARAMETER IncludeHidden
    Include hidden files and folders.
 
.PARAMETER ToClipboard
    Copy tree output to clipboard.
 
.PARAMETER DarkTheme
    Use colors optimized for dark terminals.
 
.PARAMETER OutputFile
    Path to save the output (plain or markdown depending on -MarkdownOutput).
 
.PARAMETER PlainAscii
    Disable emojis and display tree using plain ASCII.
 
.PARAMETER MarkdownOutput
    Format the output as a markdown list (suitable for GitHub).
 
.PARAMETER ShowFileSizes
    Display file sizes next to file names (except in Markdown mode).
 
.PARAMETER Favorite
    Name of a preset configuration with commonly used parameter combinations.
#>


    [CmdletBinding()]
    [Alias("tree", "lstree")]
    param (
        [Parameter(Position = 0)]
        [string]$Path = ".",

        [int]$Depth = [int]::MaxValue,

        [switch]$ShowFiles,

        [string[]]$IncludeExtensions = @(),

        [string[]]$ExcludeFolders = @(),

        [switch]$IncludeHidden,

        [switch]$ToClipboard,

        [switch]$DarkTheme,

        [string]$OutputFile,

        [switch]$PlainAscii,

        [switch]$MarkdownOutput,

        [switch]$ShowFileSizes,

        [string]$Favorite
    )

    # Favorite profiles
    $Favorites = @{
        "markdown-dev" = @{
            ShowFiles      = $true
            MarkdownOutput = $true
            ShowFileSizes  = $true
            Depth          = 5
            OutputFile     = "tree.md"
        }
        "clipboard-dark" = @{
            ShowFiles   = $true
            ToClipboard = $true
            DarkTheme   = $true
            PlainAscii  = $true
        }
    }

    if ($Favorite) {
        if ($Favorites.ContainsKey($Favorite)) {
            $preset = $Favorites[$Favorite]
            foreach ($key in $preset.Keys) {
                if (-not $PSBoundParameters.ContainsKey($key)) {
                    Set-Variable -Name $key -Value $preset[$key] -Scope Local
                }
            }
        }
        else {
            Write-Warning "Unknown favorite '$Favorite'. Available: $($Favorites.Keys -join ', ')"
        }
    }

    if (-not (Test-Path $Path)) {
        Write-Error "The specified path '$Path' does not exist."
        return
    }

    $global:OutputLines = @()
    $useEmojis = -not $PlainAscii -and -not $MarkdownOutput

    $emojiFolder = "`u{1F4C1}"  # 📁
    $emojiFile = "`u{1F4C4}"    # 📄

    $folderColor = if ($DarkTheme) { "Yellow" } else { "DarkYellow" }
    $defaultFileColor = if ($DarkTheme) { "Cyan" } else { "DarkCyan" }

    $fileColorMap = @{
        '.ps1'  = 'Green'
        '.txt'  = 'Gray'
        '.json' = 'Magenta'
        '.csv'  = 'White'
        '.log'  = 'DarkGray'
        '.xml'  = 'DarkCyan'
        '.md'   = 'Cyan'
    }

    if (-not $MarkdownOutput -and -not $PlainAscii) {
        Write-Host "Legend: " -NoNewline
        Write-Host " Folders" -ForegroundColor $folderColor -NoNewline
        Write-Host ", " -NoNewline
        Write-Host " Files" -ForegroundColor $defaultFileColor
    }

    function Get-Tree {
        param (
            [string]$BasePath,
            [string]$Indent = "",
            [int]$Level = 0
        )

        if ($Level -ge $Depth) { return }

        Write-Progress -Activity "Building Tree" -Status "Scanning $BasePath" -PercentComplete (($Level / $Depth) * 100)

        $items = Get-ChildItem -Path $BasePath -Force | Sort-Object -Property PSIsContainer, Name
        $count = $items.Count

        for ($i = 0; $i -lt $count; $i++) {
            $item = $items[$i]
            $isLast = ($i -eq $count - 1)

            if (-not $IncludeHidden -and $item.Attributes -match "Hidden") {
                continue
            }

            $connector = if ($MarkdownOutput) {
                "- "
            }
            elseif ($isLast) {
                "+--"
            }
            else {
                "|--"
            }

            $newIndent = if ($MarkdownOutput) {
                $Indent + " "
            }
            elseif ($isLast) {
                $Indent + " "
            }
            else {
                $Indent + "| "
            }

            if ($item.PSIsContainer) {
                if ($ExcludeFolders -contains $item.Name) { continue }

                $emoji = if ($useEmojis) { "$emojiFolder " } else { "" }

                $line = "$Indent$connector$emoji$($item.Name)"
                $global:OutputLines += $line
                if (-not $MarkdownOutput) {
                    Write-Host $line -ForegroundColor $folderColor
                }

                Get-Tree -BasePath $item.FullName -Indent $newIndent -Level ($Level + 1)
            }
            elseif ($ShowFiles) {
                if ($IncludeExtensions.Count -gt 0 -and ($IncludeExtensions -notcontains $item.Extension)) {
                    continue
                }

                $emoji = if ($useEmojis) { "$emojiFile " } else { "" }

                $size = [math]::Round($item.Length / 1024, 1)
                $ext = $item.Extension.ToLower()
                $color = $fileColorMap[$ext]
                if (-not $color) { $color = $defaultFileColor }

                $sizeText = if ($ShowFileSizes -and -not $MarkdownOutput) {
                    " [$size KB]"
                }
                else {
                    ""
                }

                $line = "$Indent$connector$emoji$($item.Name)$sizeText"
                $global:OutputLines += $line
                if (-not $MarkdownOutput) {
                    Write-Host $line -ForegroundColor $color
                }
            }
        }
    }

    $global:OutputLines = @()
    if ($MarkdownOutput) {
        $header = "# Directory Tree: $Path"
        $global:OutputLines += $header
    }
    else {
        Write-Host $Path -ForegroundColor Green
    }

    Get-Tree -BasePath $Path

    if ($OutputFile) {
        try {
            $outPath = $OutputFile
            if ($MarkdownOutput -and (-not $OutputFile.EndsWith(".md"))) {
                $outPath = [IO.Path]::ChangeExtension($OutputFile, "md")
            }
            $global:OutputLines | Out-File -FilePath $outPath -Encoding utf8
            Write-Host "`nTree exported to '$outPath'" -ForegroundColor Green

            if (Test-Path $outPath) {
                Start-Process notepad.exe $outPath
            }
        }
        catch {
            Write-Error "Failed to write output file: $_"
        }
    }

    if ($ToClipboard) {
        try {
            $global:OutputLines -join "`r`n" | Set-Clipboard
            Write-Host "Tree copied to clipboard" -ForegroundColor Green
        }
        catch {
            Write-Error "Failed to copy to clipboard: $_"
        }
    }
}
Export-ModuleMember -Function Show-Tree