Repository: rsteube/carapace
Pull request #98 (2020-10-11 21:02:58)

Lazy snippet


Patch:
diff --git a/bash/snippet.go b/bash/snippet.go
index d453524..4d74d3b 100644
--- a/bash/snippet.go
+++ b/bash/snippet.go
@@ -10,7 +10,21 @@ import (
 	"github.com/spf13/pflag"
 )
 
-func Snippet(cmd *cobra.Command, actions map[string]string) string {
+func snippetLazy(cmd *cobra.Command) string {
+	return fmt.Sprintf(`#!/bin/bash
+_%v_completions() {
+   source <(%v _carapace bash) 
+   _%v_completions
+}
+complete -F _%v_completions %v
+`, cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
+}
+
+func Snippet(cmd *cobra.Command, actions map[string]string, lazy bool) string {
+	if lazy {
+		return snippetLazy(cmd)
+	}
+
 	result := fmt.Sprintf(`#!/bin/bash
 _%v_callback() {
   local compline="${COMP_LINE:0:${COMP_POINT}}"
diff --git a/carapace.go b/carapace.go
index 6c58a4e..13d22fd 100644
--- a/carapace.go
+++ b/carapace.go
@@ -1,3 +1,4 @@
+// Pacakge carapace provides multi-shell completion script generation for spf13/cobra
 package carapace
 
 import (
@@ -69,8 +70,8 @@ func (c Carapace) Standalone() {
 	c.cmd.Root().SetHelpCommand(&cobra.Command{Hidden: true})
 }
 
-func (c Carapace) Snippet(shell string) string {
-	var snippet func(cmd *cobra.Command, actions map[string]string) string
+func (c Carapace) Snippet(shell string, lazy bool) string {
+	var snippet func(cmd *cobra.Command, actions map[string]string, lazy bool) string
 
 	if shell == "" {
 		shell = determineShell()
@@ -93,7 +94,7 @@ func (c Carapace) Snippet(shell string) string {
 	default:
 		return fmt.Sprintf("expected 'bash', 'elvish', 'fish', 'oil', 'powershell', 'xonsh' or 'zsh' [was: %v]", shell)
 	}
-	return snippet(c.cmd.Root(), completions.actions.Shell(shell))
+	return snippet(c.cmd.Root(), completions.actions.Shell(shell), lazy)
 }
 
 var completions = Completions{
@@ -111,7 +112,7 @@ func addCompletionCommand(cmd *cobra.Command) {
 		Hidden: true,
 		Run: func(cmd *cobra.Command, args []string) {
 			if len(args) == 0 {
-				fmt.Println(Gen(cmd).Snippet(determineShell()))
+				fmt.Println(Gen(cmd).Snippet(determineShell(), true))
 			} else {
 				if len(args) == 1 {
 					switch args[0] {
@@ -120,7 +121,7 @@ func addCompletionCommand(cmd *cobra.Command) {
 							fmt.Printf("%v:\t%v\n", uid, action)
 						}
 					default:
-						fmt.Println(Gen(cmd).Snippet(args[0]))
+						fmt.Println(Gen(cmd).Snippet(args[0], false))
 					}
 				} else {
 					targetCmd, targetArgs := findTarget(cmd)
diff --git a/elvish/snippet.go b/elvish/snippet.go
index f959826..037243c 100644
--- a/elvish/snippet.go
+++ b/elvish/snippet.go
@@ -19,7 +19,20 @@ var replacer = strings.NewReplacer( // TODO
 	`'`, `\"`,
 )
 
-func Snippet(cmd *cobra.Command, actions map[string]string) string {
+func snippetLazy(cmd *cobra.Command) string {
+	return fmt.Sprintf(`edit:completion:arg-completer[%v] = [@arg]{
+    edit:completion:arg-completer[%v] = [@arg]{}
+    eval (%v _carapace elvish | slurp)
+    $edit:completion:arg-completer[%v] $@arg
+}
+`, cmd.Name(), cmd.Name(), uid.Executable(), cmd.Name())
+}
+
+func Snippet(cmd *cobra.Command, actions map[string]string, lazy bool) string {
+	if lazy {
+		return snippetLazy(cmd)
+	}
+
 	result := fmt.Sprintf(`use str
 edit:completion:arg-completer[%v] = [@arg]{
   fn _%v_callback [uid]{
diff --git a/fish/snippet.go b/fish/snippet.go
index 5773b5f..c00d8bc 100644
--- a/fish/snippet.go
+++ b/fish/snippet.go
@@ -19,7 +19,21 @@ var replacer = strings.NewReplacer(
 	`'`, `\"`,
 )
 
-func Snippet(cmd *cobra.Command, actions map[string]string) string {
+func snippetLazy(cmd *cobra.Command) string {
+	return fmt.Sprintf(`function _%v_completions
+   complete -c %v -e
+   %v _carapace fish | source
+   complete --do-complete (commandline -cp)
+end
+complete -c %v -a '(_%v_completions)'
+complete -c %v -f
+`, cmd.Name(), cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
+}
+
+func Snippet(cmd *cobra.Command, actions map[string]string, lazy bool) string {
+	if lazy {
+		return snippetLazy(cmd)
+	}
 	result := fmt.Sprintf(`function _%v_quote_suffix
   if not commandline -cp | xargs echo 2>/dev/null >/dev/null
     if commandline -cp | sed 's/$/"/'| xargs echo 2>/dev/null >/dev/null
diff --git a/powershell/snippet.go b/powershell/snippet.go
index 2d2a790..67ad887 100644
--- a/powershell/snippet.go
+++ b/powershell/snippet.go
@@ -12,19 +12,32 @@ import (
 	"github.com/spf13/pflag"
 )
 
-func Snippet(cmd *cobra.Command, actions map[string]string) string {
+func snippetLazy(cmd *cobra.Command) string {
+	return fmt.Sprintf(`Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock {
+    param($wordToComplete, $commandAst, $cursorPosition)
+    %v _carapace powershell | Out-String | Invoke-Expression
+    & $_%v_completer $wordToComplete $commandAst $cursorPosition
+}
+`, cmd.Name(), uid.Executable(), cmd.Name())
+}
+
+func Snippet(cmd *cobra.Command, actions map[string]string, lazy bool) string {
+	if lazy {
+		return snippetLazy(cmd)
+	}
+
 	buf := new(bytes.Buffer)
 
 	var subCommandCases bytes.Buffer
 	generatePowerShellSubcommandCases(&subCommandCases, cmd, actions)
-	fmt.Fprintf(buf, powerShellCompletionTemplate, cmd.Name(), uid.Executable(), cmd.Name(), uid.Executable(), uid.Executable(), subCommandCases.String())
+	fmt.Fprintf(buf, powerShellCompletionTemplate, cmd.Name(), uid.Executable(), cmd.Name(), uid.Executable(), uid.Executable(), subCommandCases.String(), cmd.Name(), cmd.Name())
 
 	return buf.String()
 }
 
 var powerShellCompletionTemplate = `using namespace System.Management.Automation
 using namespace System.Management.Automation.Language
-Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock {
+$_%v_completer = {
     param($wordToComplete, $commandAst, $cursorPosition)
     $commandElements = $commandAst.CommandElements
     $previous = $commandElements[-1].Extent
@@ -52,7 +65,9 @@ Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock {
 
     $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
         Sort-Object -Property ListItemText
-}`
+}
+Register-ArgumentCompleter -Native -CommandName '%s' -ScriptBlock $_%v_completer
+`
 
 func generatePowerShellSubcommandCases(out io.Writer, cmd *cobra.Command, actions map[string]string) {
 	var cmdName = fmt.Sprintf("%v", uid.Command(cmd))
diff --git a/xonsh/snippet.go b/xonsh/snippet.go
index 15239ea..dfd6083 100644
--- a/xonsh/snippet.go
+++ b/xonsh/snippet.go
@@ -12,7 +12,27 @@ import (
 	"github.com/spf13/pflag"
 )
 
-func Snippet(cmd *cobra.Command, actions map[string]string) string {
+func snippetLazy(cmd *cobra.Command) string {
+	return fmt.Sprintf(`import xonsh
+import subprocess
+import builtins
+def _%v_completer(prefix, line, begidx, endidx, ctx):
+    """lazy carapace completer for %v"""
+    if not line.startswith('%v '):
+        return # not the expected command to complete
+    builtins.__xonsh__.completers = builtins.__xonsh__.completers.copy()
+    del builtins.__xonsh__.completers['%v']
+    exec(compile(subprocess.run(['%v', '_carapace', 'xonsh'], stdout=subprocess.PIPE).stdout.decode('utf-8'), "", "exec"))
+    return builtins.__xonsh__.completers['%v'](prefix, line, begidx, endidx, ctx)
+_add_one_completer('%v', _%v_completer, 'start')
+`, cmd.Name(), cmd.Name(), cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name(), cmd.Name())
+}
+
+func Snippet(cmd *cobra.Command, actions map[string]string, lazy bool) string {
+	if lazy {
+		return snippetLazy(cmd)
+	}
+
 	buf := new(bytes.Buffer)
 
 	var subCommandCases bytes.Buffer
diff --git a/zsh/snippet.go b/zsh/snippet.go
index fb2a92f..a08f7e8 100644
--- a/zsh/snippet.go
+++ b/zsh/snippet.go
@@ -19,7 +19,21 @@ var replacer = strings.NewReplacer(
 	`]`, `\]`,
 )
 
-func Snippet(cmd *cobra.Command, actions map[string]string) string {
+func snippetLazy(cmd *cobra.Command) string {
+	return fmt.Sprintf(`#compdef %v
+function _%v {
+    source <(%v _carapace zsh)
+}
+
+if compquote '' 2>/dev/null; then _%v; else compdef _%v %v; fi
+`, cmd.Name(), cmd.Name(), uid.Executable(), cmd.Name(), cmd.Name(), cmd.Name())
+}
+
+func Snippet(cmd *cobra.Command, actions map[string]string, lazy bool) string {
+	if lazy {
+		return snippetLazy(cmd)
+	}
+
 	result := fmt.Sprintf("#compdef %v\n", cmd.Name())
 	result += fmt.Sprintf(`function _%v_callback {
   # shellcheck disable=SC2086


Test patch:
diff --git a/example/cmd/root_test.go b/example/cmd/root_test.go
index 441359b..bda076d 100644
--- a/example/cmd/root_test.go
+++ b/example/cmd/root_test.go
@@ -273,7 +273,7 @@ _example_completions() {
 complete -F _example_completions example
 `
 	rootCmd.InitDefaultHelpCmd()
-	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("bash"))
+	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("bash", false))
 }
 
 func TestElvish(t *testing.T) {
@@ -454,7 +454,7 @@ edit:complex-candidate 'invalid' &display='invalid' }]
 }
 `
 	rootCmd.InitDefaultHelpCmd()
-	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("elvish"))
+	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("elvish", false))
 }
 
 func TestFish(t *testing.T) {
@@ -537,13 +537,13 @@ complete -c 'example' -f -n '_example_state _example__multiparts' -l 'slash' -d
 complete -c 'example' -f -n '_example_state _example__multiparts' -a '(_example_callback _)'
 `
 	rootCmd.InitDefaultHelpCmd()
-	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("fish"))
+	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("fish", false))
 }
 
 func TestPowershell(t *testing.T) {
 	expected := `using namespace System.Management.Automation
 using namespace System.Management.Automation.Language
-Register-ArgumentCompleter -Native -CommandName 'example' -ScriptBlock {
+$_example_completer = {
     param($wordToComplete, $commandAst, $cursorPosition)
     $commandElements = $commandAst.CommandElements
     $previous = $commandElements[-1].Extent
@@ -880,9 +880,11 @@ Register-ArgumentCompleter -Native -CommandName 'example' -ScriptBlock {
 
     $completions.Where{ $_.CompletionText -like "$wordToComplete*" } |
         Sort-Object -Property ListItemText
-}`
+}
+Register-ArgumentCompleter -Native -CommandName 'example' -ScriptBlock $_example_completer
+`
 	rootCmd.InitDefaultHelpCmd()
-	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("powershell"))
+	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("powershell", false))
 }
 
 func TestXonsh(t *testing.T) {
@@ -1208,7 +1210,7 @@ _add_one_completer('example', _example_completer, 'start')
 `
 
 	rootCmd.InitDefaultHelpCmd()
-	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("xonsh"))
+	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("xonsh", false))
 }
 
 func TestZsh(t *testing.T) {
@@ -1335,5 +1337,5 @@ function _example__multiparts {
 if compquote '' 2>/dev/null; then _example; else compdef _example example; fi
 `
 	rootCmd.InitDefaultHelpCmd()
-	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("zsh"))
+	assert.Equal(t, expected, carapace.Gen(rootCmd).Snippet("zsh", false))
 }