Skip to content

Commit 57fa2a7

Browse files
committed
added kitten
1 parent 4904d64 commit 57fa2a7

1 file changed

Lines changed: 147 additions & 0 deletions

File tree

pkg/actions/bridge/kitten.go

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
package bridge
2+
3+
import (
4+
"bytes"
5+
"encoding/json"
6+
"errors"
7+
"os/exec"
8+
"path/filepath"
9+
"strings"
10+
11+
"github.com/carapace-sh/carapace"
12+
"github.com/carapace-sh/carapace/pkg/style"
13+
)
14+
15+
// ActionKitten bridges https://github.com/kovidgoyal/kitty
16+
func ActionKitten(command ...string) carapace.Action {
17+
return actionCommand(command...)(func(command ...string) carapace.Action {
18+
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
19+
request := append(command, c.Args...)
20+
request = append(request, c.Value)
21+
22+
matchRequests := [][]string{request}
23+
if c.Value == "-" { // some longhand flags are missing when called with `-`
24+
longhandRequest := append(command, c.Args...)
25+
longhandRequest = append(longhandRequest, "--")
26+
matchRequests = append(matchRequests, longhandRequest)
27+
}
28+
29+
// partial directories aren't always completed (e.g. tmp directory with `kitty /tm`)
30+
filepathRequest := append(command, c.Args...)
31+
filepathRequest = append(filepathRequest, filepath.Dir(c.Value))
32+
matchRequests = append(matchRequests, filepathRequest)
33+
34+
m, err := json.Marshal(matchRequests)
35+
if err != nil {
36+
return carapace.ActionMessage(err.Error())
37+
}
38+
39+
var stdout, stderr bytes.Buffer
40+
cmd := c.Command("kitten", "__complete__", "json")
41+
cmd.Stdin = bytes.NewReader(m)
42+
cmd.Stdout = &stdout
43+
cmd.Stderr = &stderr
44+
45+
if err := cmd.Run(); err != nil {
46+
if exitErr, ok := err.(*exec.ExitError); ok {
47+
if firstLine := strings.SplitN(string(exitErr.Stderr), "\n", 2)[0]; strings.TrimSpace(firstLine) != "" {
48+
err = errors.New(firstLine)
49+
}
50+
}
51+
return carapace.ActionMessage(err.Error())
52+
}
53+
54+
var matchResults []kittenResult
55+
if err := json.Unmarshal(stdout.Bytes(), &matchResults); err != nil {
56+
return carapace.ActionMessage(err.Error())
57+
}
58+
59+
if len(matchResults) == 0 {
60+
return carapace.ActionValues()
61+
}
62+
63+
var nospace bool
64+
batch := carapace.Batch()
65+
for _, matchResult := range matchResults {
66+
for _, group := range matchResult.Groups {
67+
switch group.Title {
68+
case "Directories":
69+
batch = append(batch, carapace.ActionDirectories())
70+
case "Executables":
71+
dir := filepath.ToSlash(filepath.Dir(c.Value))
72+
batch = append(batch,
73+
carapace.ActionDirectories(),
74+
carapace.ActionExecutables(dir).Prefix(dir+"/"),
75+
)
76+
case "Executables in PATH":
77+
batch = append(batch, carapace.ActionExecutables())
78+
case "Options":
79+
longhandMatches := make(kittenMatches, 0)
80+
shorthandMatches := make(kittenMatches, 0)
81+
for _, match := range group.Matches {
82+
switch {
83+
case strings.HasPrefix(match.Word, "--"):
84+
longhandMatches = append(longhandMatches, match)
85+
default:
86+
shorthandMatches = append(shorthandMatches, match)
87+
}
88+
}
89+
batch = append(batch,
90+
longhandMatches.Action().Tag("longhand flags"),
91+
shorthandMatches.Action().Tag("shorthand flags"),
92+
)
93+
case "Keywords":
94+
batch = append(batch, group.Matches.Action().Tag("keywords").StyleF(style.ForKeyword))
95+
case "Sub-commands":
96+
batch = append(batch, group.Matches.Action().Tag("commands"))
97+
default:
98+
switch {
99+
case group.IsFiles:
100+
// TODO this tries to use carapace file completion via retain (switch to provided values if there is an issue)
101+
retain := make([]string, 0)
102+
for _, match := range group.Matches {
103+
retain = append(retain, filepath.ToSlash(match.Word))
104+
}
105+
batch = append(batch, carapace.ActionFiles().Retain(retain...))
106+
default:
107+
batch = append(batch, group.Matches.Action().Tag(strings.ToLower(group.Title)))
108+
}
109+
}
110+
111+
nospace = nospace || group.NoTrailingSpace
112+
}
113+
}
114+
115+
if nospace {
116+
return batch.ToA().NoSpace()
117+
}
118+
return batch.ToA()
119+
})
120+
})
121+
}
122+
123+
type kittenResult struct {
124+
Groups []struct {
125+
Title string `json:"title"`
126+
NoTrailingSpace bool `json:"no_trailing_space"`
127+
IsFiles bool `json:"is_files"`
128+
Matches kittenMatches `json:"matches"`
129+
} `json:"groups"`
130+
Delegate struct {
131+
} `json:"delegate"`
132+
}
133+
134+
type kittenMatches []struct {
135+
Word string `json:"word"`
136+
Description string `json:"description"`
137+
}
138+
139+
func (m kittenMatches) Action() carapace.Action {
140+
return carapace.ActionCallback(func(c carapace.Context) carapace.Action {
141+
vals := make([]string, 0)
142+
for _, match := range m {
143+
vals = append(vals, match.Word, match.Description)
144+
}
145+
return carapace.ActionValuesDescribed(vals...)
146+
})
147+
}

0 commit comments

Comments
 (0)