1package files
2
3import (
4 "fmt"
5 "os"
6 "sort"
7 "strings"
8
9 "github.com/charmbracelet/lipgloss/v2"
10 "github.com/charmbracelet/x/ansi"
11
12 "github.com/charmbracelet/crush/internal/config"
13 "github.com/charmbracelet/crush/internal/fsext"
14 "github.com/charmbracelet/crush/internal/history"
15 "github.com/charmbracelet/crush/internal/tui/components/core"
16 "github.com/charmbracelet/crush/internal/tui/styles"
17)
18
19// FileHistory represents a file history with initial and latest versions.
20type FileHistory struct {
21 InitialVersion history.File
22 LatestVersion history.File
23}
24
25// SessionFile represents a file with its history information.
26type SessionFile struct {
27 History FileHistory
28 FilePath string
29 Additions int
30 Deletions int
31}
32
33// RenderOptions contains options for rendering file lists.
34type RenderOptions struct {
35 MaxWidth int
36 MaxItems int
37 ShowSection bool
38 SectionName string
39}
40
41// RenderFileList renders a list of file status items with the given options.
42func RenderFileList(fileSlice []SessionFile, opts RenderOptions) []string {
43 t := styles.CurrentTheme()
44 fileList := []string{}
45
46 if opts.ShowSection {
47 sectionName := opts.SectionName
48 if sectionName == "" {
49 sectionName = "Modified Files"
50 }
51 section := t.S().Subtle.Render(sectionName)
52 fileList = append(fileList, section, "")
53 }
54
55 if len(fileSlice) == 0 {
56 fileList = append(fileList, t.S().Base.Foreground(t.Border).Render("None"))
57 return fileList
58 }
59
60 // Sort files by the latest version's created time
61 sort.Slice(fileSlice, func(i, j int) bool {
62 return fileSlice[i].History.LatestVersion.CreatedAt > fileSlice[j].History.LatestVersion.CreatedAt
63 })
64
65 // Determine how many items to show
66 maxItems := len(fileSlice)
67 if opts.MaxItems > 0 {
68 maxItems = min(opts.MaxItems, len(fileSlice))
69 }
70
71 filesShown := 0
72 for _, file := range fileSlice {
73 if file.Additions == 0 && file.Deletions == 0 {
74 continue // skip files with no changes
75 }
76 if filesShown >= maxItems {
77 break
78 }
79
80 var statusParts []string
81 if file.Additions > 0 {
82 statusParts = append(statusParts, t.S().Base.Foreground(t.Success).Render(fmt.Sprintf("+%d", file.Additions)))
83 }
84 if file.Deletions > 0 {
85 statusParts = append(statusParts, t.S().Base.Foreground(t.Error).Render(fmt.Sprintf("-%d", file.Deletions)))
86 }
87
88 extraContent := strings.Join(statusParts, " ")
89 cwd := config.Get().WorkingDir() + string(os.PathSeparator)
90 filePath := file.FilePath
91 filePath = strings.TrimPrefix(filePath, cwd)
92 filePath = fsext.DirTrim(fsext.PrettyPath(filePath), 2)
93 filePath = ansi.Truncate(filePath, opts.MaxWidth-lipgloss.Width(extraContent)-2, "β¦")
94
95 fileList = append(fileList,
96 core.Status(
97 core.StatusOpts{
98 IconColor: t.FgMuted,
99 NoIcon: true,
100 Title: filePath,
101 ExtraContent: extraContent,
102 },
103 opts.MaxWidth,
104 ),
105 )
106 filesShown++
107 }
108
109 return fileList
110}
111
112// RenderFileBlock renders a complete file block with optional truncation indicator.
113func RenderFileBlock(fileSlice []SessionFile, opts RenderOptions, showTruncationIndicator bool) string {
114 t := styles.CurrentTheme()
115 fileList := RenderFileList(fileSlice, opts)
116
117 // Add truncation indicator if needed
118 if showTruncationIndicator && opts.MaxItems > 0 {
119 totalFilesWithChanges := 0
120 for _, file := range fileSlice {
121 if file.Additions > 0 || file.Deletions > 0 {
122 totalFilesWithChanges++
123 }
124 }
125 if totalFilesWithChanges > opts.MaxItems {
126 remaining := totalFilesWithChanges - opts.MaxItems
127 if remaining == 1 {
128 fileList = append(fileList, t.S().Base.Foreground(t.FgMuted).Render("β¦"))
129 } else {
130 fileList = append(fileList,
131 t.S().Base.Foreground(t.FgSubtle).Render(fmt.Sprintf("β¦and %d more", remaining)),
132 )
133 }
134 }
135 }
136
137 content := lipgloss.JoinVertical(lipgloss.Left, fileList...)
138 if opts.MaxWidth > 0 {
139 return lipgloss.NewStyle().Width(opts.MaxWidth).Render(content)
140 }
141 return content
142}