feat: modularize powershell profile

This commit is contained in:
Stevan Freeborn
2026-07-17 22:23:00 -05:00
parent 300972fd25
commit 9cee4bcc43
7 changed files with 1121 additions and 1117 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,38 @@
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
$NVIM_CONFIG_PATH = "$env:LOCALAPPDATA\nvim"
$NVIM_CONFIG_DATA_PATH = "$env:LOCALAPPDATA\nvim-data"
$ChocolateyProfile = "$env:ChocolateyInstall\helpers\chocolateyProfile.psm1"
$env:PYTHONIOENCODING="utf-8"
$env:VIRTUAL_ENV_DISABLE_PROMPT=1
$env:EDITOR="nvim"
$WarningPreference = "SilentlyContinue"
[console]::InputEncoding = [console]::OutputEncoding = [System.Text.UTF8Encoding]::new()
function Add-UserPath {
param(
[Parameter(Mandatory=$true)]
[string]$Path
)
if (-not (Test-Path -Path $Path)) {
Write-Warning "The path '$Path' does not exist. Skipping."
return
}
$currentPath = [Environment]::GetEnvironmentVariable("Path", "User")
# Split the path into an array to check for exact matches
$pathArray = $currentPath -split ";"
if ($pathArray -notcontains $Path) {
$newPath = "$currentPath;$Path".TrimStart(';')
[Environment]::SetEnvironmentVariable("Path", $newPath, "User")
Write-Host "Successfully added '$Path' to the User Path." -ForegroundColor Cyan
} else {
Write-Host "Path already exists in the User environment variable." -ForegroundColor Yellow
}
}
@@ -0,0 +1,26 @@
if ($host.Name -eq 'ConsoleHost') {
if (Get-Module -ListAvailable -Name PSReadLine) {
Import-Module PSReadLine
}
}
if (Get-Module -ListAvailable -Name Terminal-Icons) {
Import-Module -Name Terminal-Icons
}
if (Get-Command oh-my-posh -ErrorAction SilentlyContinue) {
oh-my-posh init pwsh --config "~/.config/oh-my-posh/theme.omp.json" | Invoke-Expression
oh-my-posh toggle command
}
if (Get-Module -ListAvailable -Name posh-git) {
Import-Module posh-git
}
if ($ChocolateyProfile -and (Test-Path $ChocolateyProfile)) {
Import-Module "$ChocolateyProfile"
}
if (Get-Command zoxide -ErrorAction SilentlyContinue) {
Invoke-Expression (& { (zoxide init powershell | Out-String) })
}
@@ -0,0 +1,40 @@
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
Register-ArgumentCompleter -Native -CommandName winget -ScriptBlock {
param($wordToComplete, $commandAst, $cursorPosition)
[Console]::InputEncoding = [Console]::OutputEncoding = $OutputEncoding = [System.Text.Utf8Encoding]::new()
$Local:word = $wordToComplete.Replace('"', '""')
$Local:ast = $commandAst.ToString().Replace('"', '""')
winget complete --word="$Local:word" --commandline "$Local:ast" --position $cursorPosition | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
# PowerShell parameter completion shim for the dotnet CLI
Register-ArgumentCompleter -Native -CommandName dotnet -ScriptBlock {
param($commandName, $wordToComplete, $cursorPosition)
dotnet complete --position $cursorPosition "$wordToComplete" | ForEach-Object {
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
}
}
Import-Module PSReadLine -ErrorAction SilentlyContinue
if (Get-Command Set-PSReadLineOption -ErrorAction SilentlyContinue) {
# Auto correct 'git cmt' to 'git commit'
Set-PSReadLineOption -CommandValidationHandler {
param([CommandAst]$CommandAst)
switch ($CommandAst.GetCommandName()) {
'git' {
$gitCmd = $CommandAst.CommandElements[1].Extent
switch ($gitCmd.Text) {
'cmt' {
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(
$gitCmd.StartOffset, $gitCmd.EndOffset - $gitCmd.StartOffset, 'commit')
}
}
}
}
}
}
@@ -0,0 +1,579 @@
using namespace System.Management.Automation
using namespace System.Management.Automation.Language
Import-Module PSReadLine -ErrorAction SilentlyContinue
if (Get-Command Set-PSReadLineOption -ErrorAction SilentlyContinue) {
# Searching for commands with up/down arrow is really handy.
Set-PSReadLineKeyHandler -Key UpArrow -Function HistorySearchBackward
Set-PSReadLineKeyHandler -Key DownArrow -Function HistorySearchForward
# This key handler shows the entire or filtered history using Out-GridView.
Set-PSReadLineKeyHandler -Key F7 `
-BriefDescription History `
-LongDescription 'Show command history' `
-ScriptBlock {
$pattern = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$pattern, [ref]$null)
if ($pattern) {
$pattern = [regex]::Escape($pattern)
}
$history = [System.Collections.ArrayList]@(
$last = ''
$lines = ''
foreach ($line in [System.IO.File]::ReadLines((Get-PSReadLineOption).HistorySavePath)) {
if ($line.EndsWith('`')) {
$line = $line.Substring(0, $line.Length - 1)
$lines = if ($lines) {
"$lines`n$line"
}
else {
$line
}
continue
}
if ($lines) {
$line = "$lines`n$line"
$lines = ''
}
if (($line -cne $last) -and (!$pattern -or ($line -match $pattern))) {
$last = $line
$line
}
}
)
$history.Reverse()
$command = $history | Out-GridView -Title History -PassThru
if ($command) {
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert(($command -join "`n"))
}
}
# CaptureScreen is good for blog posts or email showing a transaction
Set-PSReadLineKeyHandler -Chord 'Ctrl+d,Ctrl+c' -Function CaptureScreen
# The built-in word movement uses character delimiters, but token based word movement is also very useful
Set-PSReadLineKeyHandler -Key Alt+d -Function ShellKillWord
Set-PSReadLineKeyHandler -Key Alt+Backspace -Function ShellBackwardKillWord
Set-PSReadLineKeyHandler -Key Alt+b -Function ShellBackwardWord
Set-PSReadLineKeyHandler -Key Alt+f -Function ShellForwardWord
Set-PSReadLineKeyHandler -Key Alt+B -Function SelectShellBackwardWord
Set-PSReadLineKeyHandler -Key Alt+F -Function SelectShellForwardWord
#region Smart Insert/Delete
Set-PSReadLineKeyHandler -Key '"', "'" `
-BriefDescription SmartInsertQuote `
-LongDescription "Insert paired quotes if not already on a quote" `
-ScriptBlock {
param($key, $arg)
$quote = $key.KeyChar
$selectionStart = $null
$selectionLength = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
# If text is selected, just quote it without any smarts
if ($selectionStart -ne -1) {
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $quote + $line.SubString($selectionStart, $selectionLength) + $quote)
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2)
return
}
$ast = $null
$tokens = $null
$parseErrors = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$parseErrors, [ref]$null)
function FindToken {
param($tokens, $cursor)
foreach ($token in $tokens) {
if ($cursor -lt $token.Extent.StartOffset) { continue }
if ($cursor -lt $token.Extent.EndOffset) {
$result = $token
$token = $token -as [StringExpandableToken]
if ($token) {
$nested = FindToken $token.NestedTokens $cursor
if ($nested) { $result = $nested }
}
return $result
}
}
return $null
}
$token = FindToken $tokens $cursor
# If we're on or inside a **quoted** string token (so not generic), we need to be smarter
if ($token -is [StringToken] -and $token.Kind -ne [TokenKind]::Generic) {
# If we're at the start of the string, assume we're inserting a new string
if ($token.Extent.StartOffset -eq $cursor) {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$quote$quote ")
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
return
}
# If we're at the end of the string, move over the closing quote if present.
if ($token.Extent.EndOffset -eq ($cursor + 1) -and $line[$cursor] -eq $quote) {
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
return
}
}
if ($null -eq $token -or
$token.Kind -eq [TokenKind]::RParen -or $token.Kind -eq [TokenKind]::RCurly -or $token.Kind -eq [TokenKind]::RBracket) {
if ($line[0..$cursor].Where{ $_ -eq $quote }.Count % 2 -eq 1) {
# Odd number of quotes before the cursor, insert a single quote
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($quote)
}
else {
# Insert matching quotes, move cursor to be in between the quotes
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$quote$quote")
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
return
}
# If cursor is at the start of a token, enclose it in quotes.
if ($token.Extent.StartOffset -eq $cursor) {
if ($token.Kind -eq [TokenKind]::Generic -or $token.Kind -eq [TokenKind]::Identifier -or
$token.Kind -eq [TokenKind]::Variable -or $token.TokenFlags.hasFlag([TokenFlags]::Keyword)) {
$end = $token.Extent.EndOffset
$len = $end - $cursor
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($cursor, $len, $quote + $line.SubString($cursor, $len) + $quote)
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($end + 2)
return
}
}
# We failed to be smart, so just insert a single quote
[Microsoft.PowerShell.PSConsoleReadLine]::Insert($quote)
}
Set-PSReadLineKeyHandler -Key '(', '{', '[' `
-BriefDescription InsertPairedBraces `
-LongDescription "Insert matching braces" `
-ScriptBlock {
param($key, $arg)
$closeChar = switch ($key.KeyChar) {
<#case#> '(' { [char]')'; break }
<#case#> '{' { [char]'}'; break }
<#case#> '[' { [char]']'; break }
}
$selectionStart = $null
$selectionLength = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($selectionStart -ne -1) {
# Text is selected, wrap it in brackets
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, $key.KeyChar + $line.SubString($selectionStart, $selectionLength) + $closeChar)
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2)
}
else {
# No text is selected, insert a pair
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)$closeChar")
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
}
Set-PSReadLineKeyHandler -Key ')', ']', '}' `
-BriefDescription SmartCloseBraces `
-LongDescription "Insert closing brace or skip" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($line[$cursor] -eq $key.KeyChar) {
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($cursor + 1)
}
else {
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("$($key.KeyChar)")
}
}
Set-PSReadLineKeyHandler -Key Backspace `
-BriefDescription SmartBackspace `
-LongDescription "Delete previous character or matching quotes/parens/braces" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($cursor -gt 0) {
$toMatch = $null
if ($cursor -lt $line.Length) {
switch ($line[$cursor]) {
<#case#> '"' { $toMatch = '"'; break }
<#case#> "'" { $toMatch = "'"; break }
<#case#> ')' { $toMatch = '('; break }
<#case#> ']' { $toMatch = '['; break }
<#case#> '}' { $toMatch = '{'; break }
}
}
if ($toMatch -ne $null -and $line[$cursor - 1] -eq $toMatch) {
[Microsoft.PowerShell.PSConsoleReadLine]::Delete($cursor - 1, 2)
}
else {
[Microsoft.PowerShell.PSConsoleReadLine]::BackwardDeleteChar($key, $arg)
}
}
}
#endregion Smart Insert/Delete
# Sometimes you enter a command but realize you forgot to do something else first.
Set-PSReadLineKeyHandler -Key Alt+w `
-BriefDescription SaveInHistory `
-LongDescription "Save current line in history but do not execute" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
[Microsoft.PowerShell.PSConsoleReadLine]::AddToHistory($line)
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
}
# Insert text from the clipboard as a here string
Set-PSReadLineKeyHandler -Key Ctrl+V `
-BriefDescription PasteAsHereString `
-LongDescription "Paste the clipboard text as a here string" `
-ScriptBlock {
param($key, $arg)
Add-Type -Assembly PresentationCore
if ([System.Windows.Clipboard]::ContainsText()) {
# Get clipboard text - remove trailing spaces, convert \r\n to \n, and remove the final \n.
$text = ([System.Windows.Clipboard]::GetText() -replace "\p{Zs}*`r?`n", "`n").TrimEnd()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("@'`n$text`n'@")
}
else {
[Microsoft.PowerShell.PSConsoleReadLine]::Ding()
}
}
# Sometimes you want to get a property of invoke a member on what you've entered so far
Set-PSReadLineKeyHandler -Key 'Alt+(' `
-BriefDescription ParenthesizeSelection `
-LongDescription "Put parenthesis around the selection or entire line and move the cursor to after the closing parenthesis" `
-ScriptBlock {
param($key, $arg)
$selectionStart = $null
$selectionLength = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetSelectionState([ref]$selectionStart, [ref]$selectionLength)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($selectionStart -ne -1) {
[Microsoft.PowerShell.PSConsoleReadLine]::Replace($selectionStart, $selectionLength, '(' + $line.SubString($selectionStart, $selectionLength) + ')')
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($selectionStart + $selectionLength + 2)
}
else {
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(0, $line.Length, '(' + $line + ')')
[Microsoft.PowerShell.PSConsoleReadLine]::EndOfLine()
}
}
# Cycle through quote styles on current token
Set-PSReadLineKeyHandler -Key "Alt+'" `
-BriefDescription ToggleQuoteArgument `
-LongDescription "Toggle quotes on the argument under the cursor" `
-ScriptBlock {
param($key, $arg)
$ast = $null
$tokens = $null
$errors = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor)
$tokenToChange = $null
foreach ($token in $tokens) {
$extent = $token.Extent
if ($extent.StartOffset -le $cursor -and $extent.EndOffset -ge $cursor) {
$tokenToChange = $token
# If the cursor is at the end of the previous token, we only want to change the previous token if there is no token under the cursor
if ($extent.EndOffset -eq $cursor -and $foreach.MoveNext()) {
$nextToken = $foreach.Current
if ($nextToken.Extent.StartOffset -eq $cursor) {
$tokenToChange = $nextToken
}
}
break
}
}
if ($tokenToChange -ne $null) {
$extent = $tokenToChange.Extent
$tokenText = $extent.Text
if ($tokenText[0] -eq '"' -and $tokenText[-1] -eq '"') {
# Switch to no quotes
$replacement = $tokenText.Substring(1, $tokenText.Length - 2)
}
elseif ($tokenText[0] -eq "'" -and $tokenText[-1] -eq "'") {
# Switch to double quotes
$replacement = '"' + $tokenText.Substring(1, $tokenText.Length - 2) + '"'
}
else {
# Add single quotes
$replacement = "'" + $tokenText + "'"
}
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(
$extent.StartOffset,
$tokenText.Length,
$replacement)
}
}
# Replace aliases on command line with full commands
Set-PSReadLineKeyHandler -Key "Alt+%" `
-BriefDescription ExpandAliases `
-LongDescription "Replace all aliases with the full command" `
-ScriptBlock {
param($key, $arg)
$ast = $null
$tokens = $null
$errors = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor)
$startAdjustment = 0
foreach ($token in $tokens) {
if ($token.TokenFlags -band [TokenFlags]::CommandName) {
$alias = $ExecutionContext.InvokeCommand.GetCommand($token.Extent.Text, 'Alias')
if ($alias -ne $null) {
$resolvedCommand = $alias.ResolvedCommandName
if ($resolvedCommand -ne $null) {
$extent = $token.Extent
$length = $extent.EndOffset - $extent.StartOffset
[Microsoft.PowerShell.PSConsoleReadLine]::Replace(
$extent.StartOffset + $startAdjustment,
$length,
$resolvedCommand)
# Adjust by the difference in length
$startAdjustment += ($resolvedCommand.Length - $length)
}
}
}
}
}
# F1 for help on the command line
Set-PSReadLineKeyHandler -Key F1 `
-BriefDescription CommandHelp `
-LongDescription "Open the help window for the current command" `
-ScriptBlock {
param($key, $arg)
$ast = $null
$tokens = $null
$errors = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$tokens, [ref]$errors, [ref]$cursor)
$commandAst = $ast.FindAll( {
$node = $args[0]
$node -is [CommandAst] -and
$node.Extent.StartOffset -le $cursor -and
$node.Extent.EndOffset -ge $cursor
}, $true) | Select-Object -Last 1
if ($commandAst -ne $null) {
$commandName = $commandAst.GetCommandName()
if ($commandName -ne $null) {
$command = $ExecutionContext.InvokeCommand.GetCommand($commandName, 'All')
if ($command -is [AliasInfo]) {
$commandName = $command.ResolvedCommandName
}
if ($commandName -ne $null) {
Get-Help $commandName -ShowWindow
}
}
}
}
# Directory Marks Jumping system
$global:PSReadLineMarks = @{}
Set-PSReadLineKeyHandler -Key Ctrl+J `
-BriefDescription MarkDirectory `
-LongDescription "Mark the current directory" `
-ScriptBlock {
param($key, $arg)
$key = [Console]::ReadKey($true)
$global:PSReadLineMarks[$key.KeyChar] = $pwd
}
Set-PSReadLineKeyHandler -Key Ctrl+j `
-BriefDescription JumpDirectory `
-LongDescription "Goto the marked directory" `
-ScriptBlock {
param($key, $arg)
$key = [Console]::ReadKey()
$dir = $global:PSReadLineMarks[$key.KeyChar]
if ($dir) {
cd $dir
[Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
}
}
Set-PSReadLineKeyHandler -Key Alt+j `
-BriefDescription ShowDirectoryMarks `
-LongDescription "Show the currently marked directories" `
-ScriptBlock {
param($key, $arg)
$global:PSReadLineMarks.GetEnumerator() | % {
[PSCustomObject]@{Key = $_.Key; Dir = $_.Value } } |
Format-Table -AutoSize | Out-Host
[Microsoft.PowerShell.PSConsoleReadLine]::InvokePrompt()
}
# Make RightArrow accept the next word in prediction suggestion instead of whole line
Set-PSReadLineKeyHandler -Key RightArrow `
-BriefDescription ForwardCharAndAcceptNextSuggestionWord `
-LongDescription "Move cursor one character to the right in the current editing line and accept the next word in suggestion when it's at the end of current editing line" `
-ScriptBlock {
param($key, $arg)
$line = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$line, [ref]$cursor)
if ($cursor -lt $line.Length) {
[Microsoft.PowerShell.PSConsoleReadLine]::ForwardChar($key, $arg)
}
else {
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptNextSuggestionWord($key, $arg)
}
}
# Cycle/Select Command Arguments
Set-PSReadLineKeyHandler -Key Alt+a `
-BriefDescription SelectCommandArguments `
-LongDescription "Set current selection to next command argument in the command line. Use of digit argument selects argument by position" `
-ScriptBlock {
param($key, $arg)
$ast = $null
$cursor = $null
[Microsoft.PowerShell.PSConsoleReadLine]::GetBufferState([ref]$ast, [ref]$null, [ref]$null, [ref]$cursor)
$asts = $ast.FindAll( {
$args[0] -is [ExpressionAst] -and
$args[0].Parent -is [CommandAst] -and
$args[0].Extent.StartOffset -ne $args[0].Parent.Extent.StartOffset
}, $true)
if ($asts.Count -eq 0) {
[Microsoft.PowerShell.PSConsoleReadLine]::Ding()
return
}
$nextAst = $null
if ($null -ne $arg) {
$nextAst = $asts[$arg - 1]
}
else {
foreach ($ast in $asts) {
if ($ast.Extent.StartOffset -ge $cursor) {
$nextAst = $ast
break
}
}
if ($null -eq $nextAst) {
$nextAst = $asts[0]
}
}
$startOffsetAdjustment = 0
$endOffsetAdjustment = 0
if ($nextAst -is [StringConstantExpressionAst] -and
$nextAst.StringConstantType -ne [StringConstantType]::BareWord) {
$startOffsetAdjustment = 1
$endOffsetAdjustment = 2
}
[Microsoft.PowerShell.PSConsoleReadLine]::SetCursorPosition($nextAst.Extent.StartOffset + $startOffsetAdjustment)
[Microsoft.PowerShell.PSConsoleReadLine]::SetMark($null, $null)
[Microsoft.PowerShell.PSConsoleReadLine]::SelectForwardChar($null, ($nextAst.Extent.EndOffset - $nextAst.Extent.StartOffset) - $endOffsetAdjustment)
}
Set-PSReadLineOption -PredictionSource History
Set-PSReadLineOption -PredictionViewStyle ListView
Set-PSReadLineOption -EditMode Windows
# dotnet build macro
Set-PSReadLineKeyHandler -Key Ctrl+Shift+b `
-BriefDescription BuildCurrentDirectory `
-LongDescription "Build the current directory" `
-ScriptBlock {
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("dotnet build")
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}
# dotnet test macro
Set-PSReadLineKeyHandler -Key Ctrl+Shift+t `
-BriefDescription BuildCurrentDirectory `
-LongDescription "Build the current directory" `
-ScriptBlock {
[Microsoft.PowerShell.PSConsoleReadLine]::RevertLine()
[Microsoft.PowerShell.PSConsoleReadLine]::Insert("dotnet test")
[Microsoft.PowerShell.PSConsoleReadLine]::AcceptLine()
}
Set-PSReadLineOption -Colors @{
"Parameter" = [ConsoleColor]::DarkBlue
}
}
function DisableReadline {
if (Get-Command Set-PSReadLineOption -ErrorAction SilentlyContinue) {
Set-PSReadLineOption -PredictionSource None
}
}
function EnableReadline {
if (Get-Command Set-PSReadLineOption -ErrorAction SilentlyContinue) {
Set-PSReadLineOption -PredictionSource History
}
}
@@ -0,0 +1,80 @@
# CD to Zoxide
if (Get-Command z -ErrorAction SilentlyContinue) {
Remove-Item alias:cd -Force -ErrorAction SilentlyContinue
Set-Alias -Name cd -Value z
}
Set-Alias -Name fromjson -Value ConvertFrom-Json -Description "Alias for ConvertFrom-Json"
Set-Alias -Name tojson -Value ConvertTo-Json -Description "Alias for ConvertTo-Json"
# Redundant aliases removed from functions.ps1 and grouped here:
New-Alias -Name catc -Value Copy-FileContent -Description "Reads file content to clipboard (cat + copy)" -Force -Scope Global
New-Alias -Name grep -Value Select-String -Description "Alias for Select-String" -Force -Scope Global
# Shorthand & Navigation functions
function Edit-InDirectory {
param(
[Parameter(Mandatory=$true)]
[string]$Directory,
[Parameter(ValueFromRemainingArguments=$true)]
[string[]]$FilePath
)
$originalLocation = Get-Location
try {
Set-Location $Directory
if ($FilePath) {
nvim $FilePath
} else {
nvim
}
}
finally {
Set-Location $originalLocation
}
}
function GotoNvimConfig {
cd $NVIM_CONFIG_PATH
}
function GotoNvimData {
cd $NVIM_CONFIG_DATA_PATH
}
function EditNvimConfig {
Edit-InDirectory $NVIM_CONFIG_PATH
}
function printJSON {
param(
[Parameter(Mandatory = $true, ValueFromPipeline = $true)]
$json
)
$json | ConvertFrom-Json | ConvertTo-Json -Depth 100
}
function fuck {
$history = (Get-History -Count 1).CommandLine;
if (-not [string]::IsNullOrWhiteSpace($history)) {
$fuck = $(thefuck $args $history);
if (-not [string]::IsNullOrWhiteSpace($fuck)) {
if ($fuck.StartsWith("echo")) { $fuck = $fuck.Substring(5); }
else { iex "$fuck"; }
}
}
[Console]::ResetColor()
}
function CopyPath {
param(
[Parameter(Mandatory=$true)]
[string]$File
)
$path = (Get-Item $File).FullName
Set-Clipboard $path
}
@@ -0,0 +1,348 @@
#region Security Tools
function Generate-JwtSecret {
<#
.SYNOPSIS
Generates a secure, Base64-encoded random string suitable for a JWT secret (HS256).
.DESCRIPTION
This function creates a cryptographically strong, random byte array of a specified length
(defaulting to 32 bytes/256 bits) and then encodes it to a Base64 string.
This is ideal for use as a symmetric key in JWTs (e.g., for HS256 algorithm).
.PARAMETER Length
Specifies the length of the random byte array in bytes.
A length of 32 bytes (256 bits) is generally recommended for HS256.
Defaults to 32 if not specified.
.EXAMPLE
Generate-JwtSecret
This will generate a 32-byte (256-bit) Base64-encoded JWT secret.
.EXAMPLE
Generate-JwtSecret -Length 64
This will generate a 64-byte (512-bit) Base64-encoded JWT secret.
#>
[CmdletBinding()]
param(
[Parameter(Position=0)]
[int]$Length = 32
)
try {
$bytes = New-Object byte[] $Length
[System.Security.Cryptography.RandomNumberGenerator]::Create().GetBytes($bytes)
[Convert]::ToBase64String($bytes)
}
catch {
Write-Error "Failed to generate JWT secret: $($_.Exception.Message)"
return $null
}
}
#endregion
#region File Management
function Copy-FileContent {
<#
.SYNOPSIS
Copies the content of a specified file to the clipboard.
.DESCRIPTION
The Copy-FileContent function reads the entire content of a specified text file
and places that content onto the system clipboard using Set-Clipboard.
This function can be invoked using its alias 'catc'.
The function is designed to be silent in terms of explicit success messages.
If an error occurs (e.g., file not found, permission issues, clipboard error),
it will throw a terminating error. This allows the calling script or user
to handle the error using standard PowerShell try/catch blocks or by checking
the $? automatic variable.
.PARAMETER Path
Specifies the path to the file whose content will be copied to the clipboard.
This parameter is mandatory. It accepts pipeline input.
.EXAMPLE
PS C:\> Copy-FileContent -Path "C:\Users\Me\Documents\MyNotes.txt"
# Copies the content of MyNotes.txt to the clipboard.
# If successful, $? will be $true. If an error occurs, it will be $false
# and an error record will be generated.
.EXAMPLE
PS C:\> catc "C:\Logs\important.log"
# Uses the alias 'catc' to copy the content of important.log to the clipboard.
.EXAMPLE
PS C:\> try {
PS C:\> catc "C:\path\to\nonexistentfile.txt"
PS C:\> Write-Host "File content copied successfully."
PS C:\> }
PS C:\> catch {
PS C:\> Write-Error "Operation failed: $($_.Exception.Message)"
PS C:\> # A calling script could use 'exit 1' here if needed
PS C:\> }
# This example shows how to catch and handle errors from the function.
.EXAMPLE
PS C:\> "C:\Temp\data.log" | catc
PS C:\> if ($?) {
PS C:\> # Optional: Perform action on success, though the function is silent.
PS C:\> } else {
PS C:\> Write-Warning "catc command failed for data.log."
PS C:\> }
# Checks the success status after execution.
.OUTPUTS
None. This function does not output any objects to the pipeline. It interacts
with the clipboard. On error, it writes an error record to the error stream.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0, ValueFromPipeline=$true, ValueFromPipelineByPropertyName=$true)]
[string]$Path
)
process {
try {
if (-not (Test-Path -Path $Path -PathType Leaf)) {
throw "File not found or not a file: '$Path'"
}
Get-Content -Path $Path -Raw -ErrorAction Stop | Set-Clipboard -ErrorAction Stop
}
catch {
throw
}
}
}
function Move-ZipsAndCleanup {
<#
.SYNOPSIS
Moves all .zip files from subdirectories to the root directory and removes empty subdirectories.
.DESCRIPTION
This function recursively searches through all subdirectories of the specified path,
moves all .zip files to the root directory, and then removes the leftover subdirectories.
If naming conflicts occur, files are automatically renamed with a counter suffix.
.PARAMETER Path
The root directory path to process. Defaults to current directory if not specified.
.EXAMPLE
Move-ZipsAndCleanup -Path "C:\MyFolder"
.EXAMPLE
Move-ZipsAndCleanup -Path "16298879069"
.EXAMPLE
Move-ZipsAndCleanup -Path "." -WhatIf
#>
[CmdletBinding(SupportsShouldProcess)]
param(
[Parameter(Position = 0)]
[string]$Path = "."
)
# Resolve the full path
$ResolvedPath = Resolve-Path $Path -ErrorAction SilentlyContinue
if (-not $ResolvedPath) {
Write-Error "Path '$Path' does not exist."
return
}
$RootPath = $ResolvedPath.Path
Write-Host "Processing directory: $RootPath" -ForegroundColor Green
# Step 1: Move all .zip files to root directory
Write-Host "Step 1: Moving .zip files to root directory..." -ForegroundColor Yellow
$ZipFiles = Get-ChildItem -Path $RootPath -Recurse -Filter "*.zip" | Where-Object { $_.Directory.FullName -ne $RootPath }
if ($ZipFiles.Count -eq 0) {
Write-Host "No .zip files found in subdirectories." -ForegroundColor Cyan
} else {
Write-Host "Found $($ZipFiles.Count) .zip file(s) to move." -ForegroundColor Cyan
foreach ($ZipFile in $ZipFiles) {
$DestinationPath = Join-Path $RootPath $ZipFile.Name
# Handle naming conflicts
if (Test-Path $DestinationPath) {
$Counter = 1
do {
$NewName = $ZipFile.BaseName + "_$Counter" + $ZipFile.Extension
$DestinationPath = Join-Path $RootPath $NewName
$Counter++
} while (Test-Path $DestinationPath)
Write-Host " Renaming due to conflict: $($ZipFile.Name) -> $(Split-Path $DestinationPath -Leaf)" -ForegroundColor Magenta
}
if ($PSCmdlet.ShouldProcess($ZipFile.FullName, "Move to $DestinationPath")) {
try {
Move-Item -Path $ZipFile.FullName -Destination $DestinationPath -Force
Write-Host " Moved: $(Split-Path $DestinationPath -Leaf)" -ForegroundColor Green
} catch {
Write-Error " Failed to move $($ZipFile.Name): $($_.Exception.Message)"
}
}
}
}
# Step 2: Remove leftover subdirectories
Write-Host "Step 2: Removing leftover subdirectories..." -ForegroundColor Yellow
$Subdirectories = Get-ChildItem -Path $RootPath -Directory
if ($Subdirectories.Count -eq 0) {
Write-Host "No subdirectories found to remove." -ForegroundColor Cyan
} else {
Write-Host "Found $($Subdirectories.Count) subdirectorie(s) to remove." -ForegroundColor Cyan
foreach ($Directory in $Subdirectories) {
if ($PSCmdlet.ShouldProcess($Directory.FullName, "Remove directory")) {
try {
Remove-Item -Path $Directory.FullName -Recurse -Force
Write-Host " Removed: $($Directory.Name)" -ForegroundColor Green
} catch {
Write-Error " Failed to remove $($Directory.Name): $($_.Exception.Message)"
}
}
}
}
Write-Host "Operation completed!" -ForegroundColor Green
}
#endregion
#region Media & Entertainment
function BRB {
$steam1Lines = @'
( (
) )
'@ -split [System.Environment]::NewLine
$steam2Lines = @'
) )
( (
'@ -split [System.Environment]::NewLine
$steamFrames = @($steam1Lines, $steam2Lines)
$frameIndex = 0
$cupLines = @'
........
| |]
\ /
`----'
'@ -split [System.Environment]::NewLine
$brbMessage = "BRB WENT TO GET COFFEE"
try {
[System.Console]::CursorVisible = $false
Clear-Host
$topPosition = [System.Console]::CursorTop
while (-not [Console]::KeyAvailable) {
[System.Console]::SetCursorPosition(0, $topPosition)
$currentSteamLines = $steamFrames[$frameIndex]
foreach ($line in $currentSteamLines) {
Write-Host $line -ForegroundColor Gray
}
Write-Host $cupLines[0] -ForegroundColor Red
Write-Host -NoNewline $cupLines[1] -ForegroundColor Red
Write-Host " $brbMessage" # Default color
Write-Host $cupLines[2] -ForegroundColor Red
Write-Host $cupLines[3] -ForegroundColor Red
$frameIndex = ($frameIndex + 1) % $steamFrames.Length
Start-Sleep -Milliseconds 1000
}
}
finally {
while ([Console]::KeyAvailable) {
[void][Console]::ReadKey($true)
}
[System.Console]::CursorVisible = $true
Clear-Host
}
}
function Get-YouTubeVideo {
<#
.SYNOPSIS
Downloads the best quality video and audio from a YouTube URL and merges them.
.DESCRIPTION
Requires yt-dlp and ffmpeg to be installed and available in your system PATH.
.PARAMETER Url
The URL of the YouTube video.
.PARAMETER OutputPath
Optional. The exact path and filename to save the video (e.g., "C:\Videos\MyVideo.mp4").
If not specified, it saves to the current directory using the video's title.
#>
[CmdletBinding()]
param (
[Parameter(Mandatory=$true, Position=0)]
[string]$Url,
[Parameter(Mandatory=$false, Position=1)]
[string]$OutputPath
)
# Verify dependencies are installed
if (-not (Get-Command "yt-dlp" -ErrorAction SilentlyContinue)) {
Write-Warning "yt-dlp is missing. Please install it (e.g., 'winget install yt-dlp') and restart your terminal."
return
}
if (-not (Get-Command "ffmpeg" -ErrorAction SilentlyContinue)) {
Write-Warning "ffmpeg is missing. It is required to merge the audio and video. Please install it (e.g., 'winget install ffmpeg') and restart your terminal."
return
}
# Set up arguments for best video (mp4) and best audio (m4a), merged into an mp4
$Arguments = @(
"-f", "bestvideo[ext=mp4]+bestaudio[ext=m4a]/best[ext=mp4]/best",
"--merge-output-format", "mp4"
)
# Handle output naming
if ($OutputPath) {
$Arguments += "-o"
$Arguments += $OutputPath
} else {
$Arguments += "-o"
$Arguments += "%(title)s.%(ext)s"
}
$Arguments += $Url
Write-Host "Starting download and merge process..." -ForegroundColor Cyan
# Execute yt-dlp with the arguments
& yt-dlp @Arguments
}
#endregion