Skip to content

Commit 4feb0db

Browse files
authored
Merge pull request #7 from britive/develop
powershell completion to main
2 parents c5833ed + f674462 commit 4feb0db

File tree

4 files changed

+127
-22
lines changed

4 files changed

+127
-22
lines changed

README.md

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
installed via the published tar balls in the GitHub repo.
1515

1616
~~~bash
17-
pip install https://github.com/britive/python-cli/releases/download/v0.1.1/pybritive-0.1.1.tar.gz
17+
pip install https://github.com/britive/python-cli/releases/download/v0.1.2/pybritive-0.1.2.tar.gz
1818
~~~
1919

2020
The end user is free to install the CLI into a virtual environment or in the global scope, so it is available
@@ -82,13 +82,19 @@ the end user wants to persist the `.britive` directory. Note that `.britive` wil
8282
that as part of the path.
8383

8484
## Shell Completion
85+
86+
TODO: Provide more automated scripts here to automatically add the required configs to the profiles. For now the below works just fine though.
87+
8588
Behind the scenes the `pybritive` CLI tool uses the python `click` package. `click` offers shell completion for
8689
the following shells.
8790

8891
* Bash
8992
* Zsh
9093
* Fish
91-
* PowerShell (work in progress for the `pybritive` cli - NOT WORKING YET)
94+
95+
A shell completion script has been written for the following shells as well.
96+
97+
* PowerShell
9298

9399
In order to set up shell completion, follow these steps. Once complete either `source` your environment again
94100
or start a new shell in order for the changes to be loaded.
@@ -130,26 +136,40 @@ _PYBRITIVE_COMPLETE=fish_source pybritive > ~/.config/fish/completions/foo-bar.f
130136
Append the below code to your PowerShell profile.
131137

132138
~~~
133-
if ((Test-Path Function:\TabExpansion) -and -not (Test-Path Function:\pybritiveTabExpansionBackup)) {
134-
Rename-Item Function:\TabExpansion pybritiveTabExpansionBackup
135-
}
136-
137-
function TabExpansion($line, $lastWord) {
138-
$lastBlock = [regex]::Split($line, '[|;]')[-1].TrimStart()
139-
$aliases = @("pybritive") + @(Get-Alias | where { $_.Definition -eq "pybritive" } | select -Exp Name)
140-
$aliasPattern = "($($aliases -join '|'))"
141-
if($lastBlock -match "^$aliasPattern ") {
142-
$Env:_PYBRITIVE_COMPLETE = "complete-powershell"
143-
$Env:COMMANDLINE = "$lastBlock"
144-
(pybritive) | ? {$_.trim() -ne "" }
145-
Remove-Item Env:_PYBRITIVE_COMPLETE
146-
Remove-Item Env:COMMANDLINE
147-
}
148-
elseif (Test-Path Function:\pybritiveTabExpansionBackup) {
149-
# Fall back on existing tab expansion
150-
pybritiveTabExpansionBackup $line $lastWord
139+
$pybritive_completion = {
140+
param($wordToComplete, $commandAst, $cursorPosition)
141+
142+
# in case of scripts, this object holds the current line after string conversion
143+
$line = "$commandAst"
144+
145+
# The behaviour of completion should depend on the trailing spaces in the current line:
146+
# * "command subcommand " --> TAB --> Completion items parameters/sub-subcommands of "subcommand"
147+
# * "command subcom" --> TAB --> Completion items to extend "subcom" into matching subcommands.
148+
# $line never contains the trailing spaces. However, $cursorPosition is the length of the original
149+
# line (with trailing spaces) in this case. This comparison allows the expected user experience.
150+
if ($cursorPosition -gt $line.Length) {
151+
$line = "$line "
151152
}
153+
154+
# set environment variables that pybritive completion will use
155+
New-Item -Path Env: -Name COMP_LINE -Value $line | Out-Null # Current line
156+
New-Item -Path Env: -Name _PYBRITIVE_COMPLETE -Value "powershell_complete" | Out-Null
157+
158+
# call pybritive and it will inspect env vars and provide completion results
159+
Invoke-Expression pybritive -ErrorAction SilentlyContinue | Tee-Object -Var completionResult | Out-Null
160+
161+
# cleanup environment variables
162+
Remove-Item Env:\COMP_LINE | Out-Null
163+
Remove-Item Env:\_PYBRITIVE_COMPLETE | Out-Null
164+
165+
# get list of completion items
166+
$items = $completionResult -split '\r?\n'
167+
168+
$items | ForEach-Object {"$_ "} # trailing space important as completion is "done"
152169
}
170+
171+
# register tab completion
172+
Register-ArgumentCompleter -Native -CommandName pybritive -ScriptBlock $pybritive_completion
153173
~~~
154174

155175

@@ -160,6 +180,8 @@ echo $profile
160180
~~~
161181

162182
And is generally something like `C:\Users\{user}\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1`.
183+
Create the file (and any needed directories) if needed.
184+
163185

164186
### Shell Completion - Profiles - Local Cache
165187

setup.cfg

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[metadata]
22
name = pybritive
3-
version = 0.1.1
3+
version = 0.1.2
44
author = Britive Inc.
55
author_email = support@britive.com
66
description = A pure Python CLI for Britive
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
1+
# register the powershell completion script since it is not required to be registered anywhere else
2+
from . import powershell_completion
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
from click.shell_completion import add_completion_class
2+
from click.shell_completion import ShellComplete, CompletionItem
3+
import os
4+
from click.parser import split_arg_string
5+
import typing as t
6+
7+
8+
# inspired by https://raw.githubusercontent.com/tibortakacs/powershell-argcomplete/master/mat.complete.ps1
9+
_powershell_source = """\
10+
$%(complete_func)s = {
11+
param($wordToComplete, $commandAst, $cursorPosition)
12+
13+
# in case of scripts, this object holds the current line after string conversion
14+
$line = "$commandAst"
15+
16+
# The behaviour of completion should depend on the trailing spaces in the current line:
17+
# * "command subcommand " --> TAB --> Completion items parameters/sub-subcommands of "subcommand"
18+
# * "command subcom" --> TAB --> Completion items to extend "subcom" into matching subcommands.
19+
# $line never contains the trailing spaces. However, $cursorPosition is the length of the original
20+
# line (with trailing spaces) in this case. This comparison allows the expected user experience.
21+
if ($cursorPosition -gt $line.Length) {
22+
$line = "$line "
23+
}
24+
25+
# set environment variables that pybritive completion will use
26+
New-Item -Path Env: -Name COMP_LINE -Value $line | Out-Null # Current line
27+
New-Item -Path Env: -Name %(complete_var)s -Value "powershell_complete" | Out-Null
28+
29+
# call %(prog_name)s and it will inspect env vars and provide completion results
30+
Invoke-Expression %(prog_name)s -ErrorAction SilentlyContinue | Tee-Object -Var completionResult | Out-Null
31+
32+
# cleanup environment variables
33+
Remove-Item Env:\COMP_LINE | Out-Null
34+
Remove-Item Env:\%(complete_var)s | Out-Null
35+
36+
# get list of completion items
37+
$items = $completionResult -split '\\r?\\n'
38+
39+
$items | ForEach-Object {"$_ "} # trailing space important as completion is "done"
40+
}
41+
42+
# register tab completion
43+
Register-ArgumentCompleter -Native -CommandName %(prog_name)s -ScriptBlock $%(complete_func)s
44+
"""
45+
46+
47+
@add_completion_class
48+
class PowershellComplete(ShellComplete):
49+
name = "powershell"
50+
source_template = _powershell_source
51+
52+
def get_completion_args(self):
53+
line = os.environ["COMP_LINE"]
54+
cwords = split_arg_string(line)
55+
num_cwords = len(cwords)
56+
args = cwords[1:num_cwords]
57+
58+
if line.endswith(' '):
59+
incomplete = ''
60+
else:
61+
try:
62+
incomplete = args.pop()
63+
except IndexError:
64+
incomplete = ''
65+
return args, incomplete
66+
67+
def format_completion(self, item: CompletionItem) -> str:
68+
value = item.value
69+
if ' ' in value:
70+
value = f'"{value}"'
71+
return f"{value}"
72+
73+
def source_vars(self) -> t.Dict[str, t.Any]:
74+
"""Vars for formatting :attr:`source_template`.
75+
By default this provides ``complete_func``, ``complete_var``,
76+
and ``prog_name``.
77+
"""
78+
return {
79+
"complete_func": self.func_name[1:], # remove leading _
80+
"complete_var": self.complete_var,
81+
"prog_name": self.prog_name
82+
}

0 commit comments

Comments
 (0)