1package commands
2
3import (
4 "fmt"
5 "sort"
6 "strings"
7
8 text "github.com/MichaelMure/go-term-text"
9 "github.com/spf13/cobra"
10
11 "github.com/MichaelMure/git-bug/bridge"
12 "github.com/MichaelMure/git-bug/bridge/core/auth"
13 "github.com/MichaelMure/git-bug/cache"
14 _select "github.com/MichaelMure/git-bug/commands/select"
15 "github.com/MichaelMure/git-bug/entities/bug"
16 "github.com/MichaelMure/git-bug/entity"
17)
18
19type validArgsFunction func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective)
20
21func completionHandlerError(err error) (completions []string, directives cobra.ShellCompDirective) {
22 return nil, cobra.ShellCompDirectiveError
23}
24
25func completeBridge(env *Env) validArgsFunction {
26 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
27 if err := loadBackend(env)(cmd, args); err != nil {
28 return completionHandlerError(err)
29 }
30 defer func() {
31 _ = env.backend.Close()
32 }()
33
34 bridges, err := bridge.ConfiguredBridges(env.backend)
35 if err != nil {
36 return completionHandlerError(err)
37 }
38
39 completions = make([]string, len(bridges))
40 for i, bridge := range bridges {
41 completions[i] = bridge + "\t" + "Bridge"
42 }
43
44 return completions, cobra.ShellCompDirectiveNoFileComp
45 }
46}
47
48func completeBridgeAuth(env *Env) validArgsFunction {
49 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
50 if err := loadBackend(env)(cmd, args); err != nil {
51 return completionHandlerError(err)
52 }
53 defer func() {
54 _ = env.backend.Close()
55 }()
56
57 creds, err := auth.List(env.backend)
58 if err != nil {
59 return completionHandlerError(err)
60 }
61
62 completions = make([]string, len(creds))
63 for i, cred := range creds {
64 meta := make([]string, 0, len(cred.Metadata()))
65 for k, v := range cred.Metadata() {
66 meta = append(meta, k+":"+v)
67 }
68 sort.Strings(meta)
69 metaFmt := strings.Join(meta, ",")
70
71 completions[i] = cred.ID().Human() + "\t" + cred.Target() + " " + string(cred.Kind()) + " " + metaFmt
72 }
73
74 return completions, cobra.ShellCompDirectiveNoFileComp
75 }
76}
77
78func completeBug(env *Env) validArgsFunction {
79 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
80 if err := loadBackend(env)(cmd, args); err != nil {
81 return completionHandlerError(err)
82 }
83 defer func() {
84 _ = env.backend.Close()
85 }()
86
87 return completeBugWithBackend(env.backend, toComplete)
88 }
89}
90
91func completeBugWithBackend(backend *cache.RepoCache, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
92 allIds := backend.AllBugsIds()
93 bugExcerpt := make([]*cache.BugExcerpt, len(allIds))
94 for i, id := range allIds {
95 var err error
96 bugExcerpt[i], err = backend.ResolveBugExcerpt(id)
97 if err != nil {
98 return completionHandlerError(err)
99 }
100 }
101
102 for i, id := range allIds {
103 if strings.Contains(id.String(), strings.TrimSpace(toComplete)) {
104 completions = append(completions, id.Human()+"\t"+bugExcerpt[i].Title)
105 }
106 }
107
108 return completions, cobra.ShellCompDirectiveNoFileComp
109}
110
111func completeBugAndLabels(env *Env, addOrRemove bool) validArgsFunction {
112 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
113 if err := loadBackend(env)(cmd, args); err != nil {
114 return completionHandlerError(err)
115 }
116 defer func() {
117 _ = env.backend.Close()
118 }()
119
120 b, args, err := _select.ResolveBug(env.backend, args)
121 if err == _select.ErrNoValidId {
122 // we need a bug first to complete labels
123 return completeBugWithBackend(env.backend, toComplete)
124 }
125 if err != nil {
126 return completionHandlerError(err)
127 }
128
129 snap := b.Snapshot()
130
131 seenLabels := map[bug.Label]bool{}
132 for _, label := range args {
133 seenLabels[bug.Label(label)] = addOrRemove
134 }
135
136 var labels []bug.Label
137 if addOrRemove {
138 for _, label := range snap.Labels {
139 seenLabels[label] = true
140 }
141
142 allLabels := env.backend.ValidLabels()
143 labels = make([]bug.Label, 0, len(allLabels))
144 for _, label := range allLabels {
145 if !seenLabels[label] {
146 labels = append(labels, label)
147 }
148 }
149 } else {
150 labels = make([]bug.Label, 0, len(snap.Labels))
151 for _, label := range snap.Labels {
152 if seenLabels[label] {
153 labels = append(labels, label)
154 }
155 }
156 }
157
158 completions = make([]string, len(labels))
159 for i, label := range labels {
160 completions[i] = string(label) + "\t" + "Label"
161 }
162
163 return completions, cobra.ShellCompDirectiveNoFileComp
164 }
165}
166
167func completeComment(env *Env) validArgsFunction {
168 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
169 if err := loadBackend(env)(cmd, args); err != nil {
170 return completionHandlerError(err)
171 }
172 defer func() {
173 _ = env.backend.Close()
174 }()
175
176 bugPrefix, _ := entity.SeparateIds(toComplete)
177 bugCandidate := make([]entity.Id, 0, 5)
178
179 // build a list of possible matching bugs
180 _, _ = env.backend.ResolveBugExcerptMatcher(func(excerpt *cache.BugExcerpt) bool {
181 if excerpt.Id.HasPrefix(bugPrefix) {
182 bugCandidate = append(bugCandidate, excerpt.Id)
183 }
184 return false
185 })
186
187 completions = make([]string, 0, 5)
188
189 for _, bugId := range bugCandidate {
190 b, err := env.backend.ResolveBug(bugId)
191 if err != nil {
192 return completionHandlerError(err)
193 }
194
195 for _, comment := range b.Snapshot().Comments {
196 if comment.CombinedId().HasPrefix(toComplete) {
197 text.TrimSpace()
198 message := strings.ReplaceAll(comment.Message, "\n", "")
199
200 completions = append(completions, comment.CombinedId().Human()+"\t"+text.TruncateMax())
201 }
202 }
203 }
204
205 }
206}
207
208func completeFrom(choices []string) validArgsFunction {
209 return func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
210 return choices, cobra.ShellCompDirectiveNoFileComp
211 }
212}
213
214func completeGitRemote(env *Env) validArgsFunction {
215 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
216 if err := loadBackend(env)(cmd, args); err != nil {
217 return completionHandlerError(err)
218 }
219 defer func() {
220 _ = env.backend.Close()
221 }()
222
223 remoteMap, err := env.backend.GetRemotes()
224 if err != nil {
225 return completionHandlerError(err)
226 }
227 completions = make([]string, 0, len(remoteMap))
228 for remote, url := range remoteMap {
229 completions = append(completions, remote+"\t"+"Remote: "+url)
230 }
231 sort.Strings(completions)
232 return completions, cobra.ShellCompDirectiveNoFileComp
233 }
234}
235
236func completeLabel(env *Env) validArgsFunction {
237 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
238 if err := loadBackend(env)(cmd, args); err != nil {
239 return completionHandlerError(err)
240 }
241 defer func() {
242 _ = env.backend.Close()
243 }()
244
245 labels := env.backend.ValidLabels()
246 completions = make([]string, len(labels))
247 for i, label := range labels {
248 if strings.Contains(label.String(), " ") {
249 completions[i] = fmt.Sprintf("\"%s\"\tLabel", label.String())
250 } else {
251 completions[i] = fmt.Sprintf("%s\tLabel", label.String())
252 }
253 }
254 return completions, cobra.ShellCompDirectiveNoFileComp
255 }
256}
257
258func completeLs(env *Env) validArgsFunction {
259 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
260 if strings.HasPrefix(toComplete, "status:") {
261 completions = append(completions, "status:open\tOpen bugs")
262 completions = append(completions, "status:closed\tClosed bugs")
263 return completions, cobra.ShellCompDirectiveDefault
264 }
265
266 byPerson := []string{"author:", "participant:", "actor:"}
267 byLabel := []string{"label:", "no:"}
268 needBackend := false
269 for _, key := range append(byPerson, byLabel...) {
270 if strings.HasPrefix(toComplete, key) {
271 needBackend = true
272 }
273 }
274
275 if needBackend {
276 if err := loadBackend(env)(cmd, args); err != nil {
277 return completionHandlerError(err)
278 }
279 defer func() {
280 _ = env.backend.Close()
281 }()
282 }
283
284 for _, key := range byPerson {
285 if !strings.HasPrefix(toComplete, key) {
286 continue
287 }
288 ids := env.backend.AllIdentityIds()
289 completions = make([]string, len(ids))
290 for i, id := range ids {
291 user, err := env.backend.ResolveIdentityExcerpt(id)
292 if err != nil {
293 return completionHandlerError(err)
294 }
295 var handle string
296 if user.Login != "" {
297 handle = user.Login
298 } else {
299 // "author:John Doe" does not work yet, so use the first name.
300 handle = strings.Split(user.Name, " ")[0]
301 }
302 completions[i] = key + handle + "\t" + user.DisplayName()
303 }
304 return completions, cobra.ShellCompDirectiveNoFileComp
305 }
306
307 for _, key := range byLabel {
308 if !strings.HasPrefix(toComplete, key) {
309 continue
310 }
311 labels := env.backend.ValidLabels()
312 completions = make([]string, len(labels))
313 for i, label := range labels {
314 if strings.Contains(label.String(), " ") {
315 completions[i] = key + "\"" + string(label) + "\""
316 } else {
317 completions[i] = key + string(label)
318 }
319 }
320 return completions, cobra.ShellCompDirectiveNoFileComp
321 }
322
323 completions = []string{
324 "actor:\tFilter by actor",
325 "author:\tFilter by author",
326 "label:\tFilter by label",
327 "no:\tExclude bugs by label",
328 "participant:\tFilter by participant",
329 "status:\tFilter by open/close status",
330 "title:\tFilter by title",
331 }
332 return completions, cobra.ShellCompDirectiveNoSpace
333 }
334}
335
336func completeUser(env *Env) validArgsFunction {
337 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
338 if err := loadBackend(env)(cmd, args); err != nil {
339 return completionHandlerError(err)
340 }
341 defer func() {
342 _ = env.backend.Close()
343 }()
344
345 ids := env.backend.AllIdentityIds()
346 completions = make([]string, len(ids))
347 for i, id := range ids {
348 user, err := env.backend.ResolveIdentityExcerpt(id)
349 if err != nil {
350 return completionHandlerError(err)
351 }
352 completions[i] = user.Id.Human() + "\t" + user.DisplayName()
353 }
354 return completions, cobra.ShellCompDirectiveNoFileComp
355 }
356}
357
358func completeUserForQuery(env *Env) validArgsFunction {
359 return func(cmd *cobra.Command, args []string, toComplete string) (completions []string, directives cobra.ShellCompDirective) {
360 if err := loadBackend(env)(cmd, args); err != nil {
361 return completionHandlerError(err)
362 }
363 defer func() {
364 _ = env.backend.Close()
365 }()
366
367 ids := env.backend.AllIdentityIds()
368 completions = make([]string, len(ids))
369 for i, id := range ids {
370 user, err := env.backend.ResolveIdentityExcerpt(id)
371 if err != nil {
372 return completionHandlerError(err)
373 }
374 var handle string
375 if user.Login != "" {
376 handle = user.Login
377 } else {
378 // "author:John Doe" does not work yet, so use the first name.
379 handle = strings.Split(user.Name, " ")[0]
380 }
381 completions[i] = handle + "\t" + user.DisplayName()
382 }
383 return completions, cobra.ShellCompDirectiveNoFileComp
384 }
385}