1package common
2
3import (
4 "fmt"
5 "image"
6 "os"
7
8 tea "charm.land/bubbletea/v2"
9 "github.com/atotto/clipboard"
10 "github.com/charmbracelet/crush/internal/app"
11 "github.com/charmbracelet/crush/internal/config"
12 "github.com/charmbracelet/crush/internal/ui/styles"
13 "github.com/charmbracelet/crush/internal/ui/util"
14 uv "github.com/charmbracelet/ultraviolet"
15)
16
17// MaxAttachmentSize defines the maximum allowed size for file attachments (5 MB).
18const MaxAttachmentSize = int64(5 * 1024 * 1024)
19
20// AllowedImageTypes defines the permitted image file types.
21var AllowedImageTypes = []string{".jpg", ".jpeg", ".png"}
22
23// Common defines common UI options and configurations.
24type Common struct {
25 App *app.App
26 Styles *styles.Styles
27}
28
29// Config returns the pure-data configuration associated with this [Common] instance.
30func (c *Common) Config() *config.Config {
31 return c.App.Config()
32}
33
34// Store returns the config store associated with this [Common] instance.
35func (c *Common) Store() *config.ConfigStore {
36 return c.App.Store()
37}
38
39// DefaultCommon returns the default common UI configurations.
40func DefaultCommon(app *app.App) *Common {
41 s := styles.DefaultStyles()
42 return &Common{
43 App: app,
44 Styles: &s,
45 }
46}
47
48// CenterRect returns a new [Rectangle] centered within the given area with the
49// specified width and height.
50func CenterRect(area uv.Rectangle, width, height int) uv.Rectangle {
51 centerX := area.Min.X + area.Dx()/2
52 centerY := area.Min.Y + area.Dy()/2
53 minX := centerX - width/2
54 minY := centerY - height/2
55 maxX := minX + width
56 maxY := minY + height
57 return image.Rect(minX, minY, maxX, maxY)
58}
59
60// BottomLeftRect returns a new [Rectangle] positioned at the bottom-left within the given area with the
61// specified width and height.
62func BottomLeftRect(area uv.Rectangle, width, height int) uv.Rectangle {
63 minX := area.Min.X
64 maxX := minX + width
65 maxY := area.Max.Y
66 minY := maxY - height
67 return image.Rect(minX, minY, maxX, maxY)
68}
69
70// IsFileTooBig checks if the file at the given path exceeds the specified size
71// limit.
72func IsFileTooBig(filePath string, sizeLimit int64) (bool, error) {
73 fileInfo, err := os.Stat(filePath)
74 if err != nil {
75 return false, fmt.Errorf("error getting file info: %w", err)
76 }
77
78 if fileInfo.Size() > sizeLimit {
79 return true, nil
80 }
81
82 return false, nil
83}
84
85// CopyToClipboard copies the given text to the clipboard using both OSC 52
86// (terminal escape sequence) and native clipboard for maximum compatibility.
87// Returns a command that reports success to the user with the given message.
88func CopyToClipboard(text, successMessage string) tea.Cmd {
89 return CopyToClipboardWithCallback(text, successMessage, nil)
90}
91
92// CopyToClipboardWithCallback copies text to clipboard and executes a callback
93// before showing the success message.
94// This is useful when you need to perform additional actions like clearing UI state.
95func CopyToClipboardWithCallback(text, successMessage string, callback tea.Cmd) tea.Cmd {
96 return tea.Sequence(
97 tea.SetClipboard(text),
98 func() tea.Msg {
99 _ = clipboard.WriteAll(text)
100 return nil
101 },
102 callback,
103 util.ReportInfo(successMessage),
104 )
105}