renderInteractive shows the file tree, then a picker to browse individual files.
(opts *PreviewOptions, cs *iostreams.ColorScheme, skill discovery.Skill, files []discovery.SkillFile, renderedSkillMD string, extraFiles []discovery.SkillFile, apiClient *api.Client, hostname, owner, repo string)
| 315 | |
| 316 | // renderInteractive shows the file tree, then a picker to browse individual files. |
| 317 | func renderInteractive(opts *PreviewOptions, cs *iostreams.ColorScheme, skill discovery.Skill, |
| 318 | files []discovery.SkillFile, renderedSkillMD string, extraFiles []discovery.SkillFile, |
| 319 | apiClient *api.Client, hostname, owner, repo string) { |
| 320 | |
| 321 | // Show the file tree to stderr so it persists above the prompt |
| 322 | fmt.Fprintf(opts.IO.ErrOut, "\n%s\n", cs.Bold(skill.DisplayName()+"/")) |
| 323 | renderFileTree(opts.IO.ErrOut, cs, files) |
| 324 | fmt.Fprintln(opts.IO.ErrOut) |
| 325 | |
| 326 | // Build choices: SKILL.md first, then extra files |
| 327 | choices := make([]string, 0, len(extraFiles)+1) |
| 328 | choices = append(choices, "SKILL.md") |
| 329 | for _, f := range extraFiles { |
| 330 | choices = append(choices, f.Path) |
| 331 | } |
| 332 | |
| 333 | // Save original stdout. StopPager closes IO.Out, so we need to |
| 334 | // restore a working writer before each StartPager call. |
| 335 | originalOut := opts.IO.Out |
| 336 | |
| 337 | for { |
| 338 | // Restore original Out before each pager cycle. StartPager replaces |
| 339 | // IO.Out with a pipe; StopPager closes that pipe but does not |
| 340 | // restore the original. The original writer remains valid. |
| 341 | opts.IO.Out = originalOut |
| 342 | |
| 343 | idx, err := opts.Prompter.Select("View a file (Esc to exit):", "", choices) |
| 344 | if err != nil { |
| 345 | return // Prompter returns error on Esc/Ctrl-C; treat as graceful exit |
| 346 | } |
| 347 | |
| 348 | var content string |
| 349 | |
| 350 | if idx == 0 { |
| 351 | content = renderedSkillMD |
| 352 | } else { |
| 353 | selectedFile := extraFiles[idx-1] |
| 354 | |
| 355 | // Fetch on demand; don't hold blob data in memory |
| 356 | fileContent, fetchErr := discovery.FetchBlob(apiClient, hostname, owner, repo, selectedFile.SHA) |
| 357 | if fetchErr != nil { |
| 358 | fmt.Fprintf(opts.IO.ErrOut, "%s could not fetch %s: %v\n", cs.Red("!"), selectedFile.Path, fetchErr) |
| 359 | continue |
| 360 | } |
| 361 | content = renderSelectedFilePreview(opts, selectedFile.Path, fileContent) |
| 362 | if !strings.HasSuffix(content, "\n") { |
| 363 | content += "\n" |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | if err := opts.IO.StartPager(); err != nil { |
| 368 | fmt.Fprintf(opts.IO.ErrOut, "starting pager failed: %v\n", err) |
| 369 | } |
| 370 | fmt.Fprint(opts.IO.Out, content) |
| 371 | opts.IO.StopPager() |
| 372 | } |
| 373 | } |
| 374 |
no test coverage detected