diff --git a/Library/Homebrew/caveats.rb b/Library/Homebrew/caveats.rb index 1769f7ce76..b843926058 100644 --- a/Library/Homebrew/caveats.rb +++ b/Library/Homebrew/caveats.rb @@ -29,7 +29,7 @@ class Caveats end caveats << keg_only_text - valid_shells = [:bash, :zsh, :fish].freeze + valid_shells = [:bash, :zsh, :fish, :pwsh].freeze current_shell = Utils::Shell.preferred || Utils::Shell.parent shells = if current_shell.present? && (shell_sym = current_shell.to_sym) && @@ -143,6 +143,11 @@ class Caveats zsh #{installed.join(" and ")} have been installed to: #{root_dir}/share/zsh/site-functions EOS + when :pwsh + <<~EOS + PowerShell completion has been installed to: + #{root_dir}/share/pwsh/completions + EOS end end diff --git a/Library/Homebrew/formula.rb b/Library/Homebrew/formula.rb index 1770c1344b..74b5e957b4 100644 --- a/Library/Homebrew/formula.rb +++ b/Library/Homebrew/formula.rb @@ -1137,6 +1137,13 @@ class Formula sig { returns(Pathname) } def fish_completion = share/"fish/vendor_completions.d" + # The directory where formula's powershell completion files should be + # installed. + # This is symlinked into `HOMEBREW_PREFIX` after installation or with + # `brew link` for formulae that are not keg-only. + sig { returns(Pathname) } + def pwsh_completion = share/"pwsh/completions" + # The directory used for as the prefix for {#etc} and {#var} files on # installation so, despite not being in `HOMEBREW_CELLAR`, they are installed # there after pouring a bottle. @@ -1989,7 +1996,7 @@ class Formula end private :extract_macho_slice_from - # Generate shell completions for a formula for `bash`, `zsh` and `fish`, using the formula's executable. + # Generate shell completions for a formula for `bash`, `zsh`, `fish`, and `powershell`, using the formula's executable. # # ### Examples # @@ -2003,6 +2010,8 @@ class Formula # (zsh_completion/"_foo").write Utils.safe_popen_read({ "SHELL" => "zsh" }, bin/"foo", "completions", "zsh") # (fish_completion/"foo.fish").write Utils.safe_popen_read({ "SHELL" => "fish" }, bin/"foo", # "completions", "fish") + # (pwsh_completion/"foo").write Utils.safe_popen_read({ "SHELL" => "pwsh" }, bin/"foo", + # "completions", "powershell") # ``` # # Selecting shells and using a different `base_name`. @@ -2094,7 +2103,7 @@ class Formula } def generate_completions_from_executable(*commands, base_name: nil, - shells: [:bash, :zsh, :fish], + shells: [:bash, :zsh, :fish, :pwsh], shell_parameter_format: nil) executable = commands.first.to_s base_name ||= File.basename(executable) if executable.start_with?(bin.to_s, sbin.to_s) @@ -2104,28 +2113,31 @@ class Formula bash: bash_completion/base_name, zsh: zsh_completion/"_#{base_name}", fish: fish_completion/"#{base_name}.fish", + pwsh: pwsh_completion/"#{base_name}.ps1", } shells.each do |shell| popen_read_env = { "SHELL" => shell.to_s } script_path = completion_script_path_map[shell] + # Go's cobra and Rust's clap accept "powershell". + shell_argument = shell == :pwsh ? "powershell" : shell.to_s shell_parameter = if shell_parameter_format.nil? - shell.to_s + shell_argument.to_s elsif shell_parameter_format == :flag - "--#{shell}" + "--#{shell_argument}" elsif shell_parameter_format == :arg - "--shell=#{shell}" + "--shell=#{shell_argument}" elsif shell_parameter_format == :none nil elsif shell_parameter_format == :click prog_name = File.basename(executable).upcase.tr("-", "_") - popen_read_env["_#{prog_name}_COMPLETE"] = "#{shell}_source" + popen_read_env["_#{prog_name}_COMPLETE"] = "#{shell_argument}_source" nil elsif shell_parameter_format == :clap - popen_read_env["COMPLETE"] = shell.to_s + popen_read_env["COMPLETE"] = shell_argument.to_s nil else - "#{shell_parameter_format}#{shell}" + "#{shell_parameter_format}#{shell_argument}" end popen_read_args = %w[] diff --git a/Library/Homebrew/keg.rb b/Library/Homebrew/keg.rb index b28810e9d8..c7035fcf8c 100644 --- a/Library/Homebrew/keg.rb +++ b/Library/Homebrew/keg.rb @@ -147,6 +147,7 @@ class Keg share/man/man1 share/man/man2 share/man/man3 share/man/man4 share/man/man5 share/man/man6 share/man/man7 share/man/man8 share/zsh share/zsh/site-functions + share/pwsh share/pwsh/completions var/log ].map { |dir| HOMEBREW_PREFIX/dir } + must_exist_subdirectories + [ HOMEBREW_CACHE, @@ -354,6 +355,7 @@ class Keg when :zsh dir = path/"share/zsh/site-functions" dir if dir.directory? && dir.children.any? { |f| f.basename.to_s.start_with?("_") } + when :pwsh then path/"share/pwsh/completions" end dir&.directory? && !dir.children.empty? end diff --git a/Library/Homebrew/rubocops/lines.rb b/Library/Homebrew/rubocops/lines.rb index 609188e4a1..698aee4fc1 100644 --- a/Library/Homebrew/rubocops/lines.rb +++ b/Library/Homebrew/rubocops/lines.rb @@ -533,7 +533,7 @@ module RuboCop correctable_shell_completion_node(install) do |node, shell, base_name, executable, subcmd, shell_parameter| # generate_completions_from_executable only applicable if shell is passed - next unless shell_parameter.match?(/(bash|zsh|fish)/) + next unless shell_parameter.match?(/(bash|zsh|fish|pwsh)/) base_name = base_name.delete_prefix("_").delete_suffix(".fish") shell = shell.to_s.delete_suffix("_completion").to_sym @@ -541,6 +541,7 @@ module RuboCop .delete_suffix("bash") .delete_suffix("zsh") .delete_suffix("fish") + .delete_suffix("pwsh") shell_parameter_format = if shell_parameter_stripped.empty? nil elsif shell_parameter_stripped == "--" diff --git a/Library/Homebrew/test/caveats_spec.rb b/Library/Homebrew/test/caveats_spec.rb index c9231e2615..7871177928 100644 --- a/Library/Homebrew/test/caveats_spec.rb +++ b/Library/Homebrew/test/caveats_spec.rb @@ -248,6 +248,7 @@ RSpec.describe Caveats do let(:bash_completion_dir) { path/"etc/bash_completion.d" } let(:fish_vendor_completions) { path/"share/fish/vendor_completions.d" } let(:zsh_site_functions) { path/"share/zsh/site-functions" } + let(:pwsh_completion_dir) { path/"share/pwsh/completions" } before do # don't try to load/fetch gcc/glibc @@ -274,6 +275,12 @@ RSpec.describe Caveats do FileUtils.touch zsh_site_functions/f.name expect(caveats).to include(HOMEBREW_PREFIX/"share/zsh/site-functions") end + + it "includes where pwsh completions have been installed to" do + pwsh_completion_dir.mkpath + FileUtils.touch pwsh_completion_dir/f.name + expect(caveats).to include(HOMEBREW_PREFIX/"share/pwsh/completions") + end end end end diff --git a/Library/Homebrew/test/utils/shell_spec.rb b/Library/Homebrew/test/utils/shell_spec.rb index 16bb3670a3..3855f08131 100644 --- a/Library/Homebrew/test/utils/shell_spec.rb +++ b/Library/Homebrew/test/utils/shell_spec.rb @@ -35,6 +35,11 @@ RSpec.describe Utils::Shell do ENV["SHELL"] = "/bin/ksh" expect(described_class.profile).to eq("~/.kshrc") end + + it "returns ~/.config/powershell/Microsoft.PowerShell_profile.ps1 for PowerShell" do + ENV["SHELL"] = "/usr/bin/pwsh" + expect(described_class.profile).to eq("~/.config/powershell/Microsoft.PowerShell_profile.ps1") + end end describe "::from_path" do diff --git a/Library/Homebrew/utils/shell.rb b/Library/Homebrew/utils/shell.rb index eb429ab7a6..1e88d881dd 100644 --- a/Library/Homebrew/utils/shell.rb +++ b/Library/Homebrew/utils/shell.rb @@ -17,7 +17,7 @@ module Utils shell_name = File.basename(path) # handle possible version suffix like `zsh-5.2` shell_name.sub!(/-.*\z/m, "") - shell_name.to_sym if %w[bash csh fish ksh mksh rc sh tcsh zsh].include?(shell_name) + shell_name.to_sym if %w[bash csh fish ksh mksh pwsh rc sh tcsh zsh].include?(shell_name) end sig { params(default: String).returns(String) } @@ -60,6 +60,9 @@ module Utils when :bash bash_profile = "#{Dir.home}/.bash_profile" return bash_profile if File.exist? bash_profile + when :pwsh + pwsh_profile = "#{Dir.home}/.config/powershell/Microsoft.PowerShell_profile.ps1" + return pwsh_profile if File.exist? pwsh_profile when :rc rc_profile = "#{Dir.home}/.rcrc" return rc_profile if File.exist? rc_profile @@ -78,6 +81,8 @@ module Utils case preferred when :bash, :ksh, :sh, :zsh, nil "echo 'export #{variable}=#{sh_quote(value)}' >> #{profile}" + when :pwsh + "$env:#{variable}='#{value}' >> #{profile}" when :rc "echo '#{variable}=(#{sh_quote(value)})' >> #{profile}" when :csh, :tcsh @@ -92,6 +97,8 @@ module Utils case preferred when :bash, :ksh, :mksh, :sh, :zsh, nil "echo 'export PATH=\"#{sh_quote(path)}:$PATH\"' >> #{profile}" + when :pwsh + "$env:PATH = '#{path}' + \":${env:PATH}\" >> #{profile}" when :rc "echo 'path=(#{sh_quote(path)} $path)' >> #{profile}" when :csh, :tcsh @@ -108,6 +115,7 @@ module Utils fish: "~/.config/fish/config.fish", ksh: "~/.kshrc", mksh: "~/.kshrc", + pwsh: "~/.config/powershell/Microsoft.PowerShell_profile.ps1", rc: "~/.rcrc", sh: "~/.profile", tcsh: "~/.tcshrc", diff --git a/docs/Shell-Completion.md b/docs/Shell-Completion.md index 91ada47368..125966f99b 100644 --- a/docs/Shell-Completion.md +++ b/docs/Shell-Completion.md @@ -74,3 +74,15 @@ if test -d (brew --prefix)"/share/fish/vendor_completions.d" set -p fish_complete_path (brew --prefix)/share/fish/vendor_completions.d end ``` + +## Configuring Completions in `pwsh` + +To make Homebrew's completions available in `pwsh` (PowerShell), you must source the definitions as part of your shell's startup. Add the following to your `$PROFILE`, for example: `~/.config/powershell/Microsoft.PowerShell_profile.ps1`: + +```pwsh +if ((Get-Command brew) -and (Test-Path ($completions = "$(brew --prefix)/share/pwsh/completions"))) { + foreach ($f in Get-ChildItem -Path $completions -File) { + . $f + } +} +```