22package jobdetail
33
44import (
5- "encoding/json"
65 "fmt"
76 "math"
87 "strings"
@@ -11,8 +10,10 @@ import (
1110 "charm.land/bubbles/v2/key"
1211 tea "charm.land/bubbletea/v2"
1312 "charm.land/lipgloss/v2"
13+ "github.com/charmbracelet/x/ansi"
1414 "github.com/kpumuk/lazykiq/internal/sidekiq"
1515 "github.com/kpumuk/lazykiq/internal/ui/components/frame"
16+ "github.com/kpumuk/lazykiq/internal/ui/components/jsonview"
1617 "github.com/kpumuk/lazykiq/internal/ui/format"
1718)
1819
@@ -73,27 +74,39 @@ func DefaultKeyMap() KeyMap {
7374
7475// Styles holds styles for the job detail component.
7576type Styles struct {
76- Title lipgloss.Style
77- Label lipgloss.Style
78- Value lipgloss.Style
79- JSON lipgloss.Style
80- Border lipgloss.Style
81- PanelTitle lipgloss.Style
82- FocusBorder lipgloss.Style
83- Muted lipgloss.Style
77+ Title lipgloss.Style
78+ Label lipgloss.Style
79+ Value lipgloss.Style
80+ JSON lipgloss.Style
81+ JSONKey lipgloss.Style
82+ JSONString lipgloss.Style
83+ JSONNumber lipgloss.Style
84+ JSONBool lipgloss.Style
85+ JSONNull lipgloss.Style
86+ JSONPunctuation lipgloss.Style
87+ Border lipgloss.Style
88+ PanelTitle lipgloss.Style
89+ FocusBorder lipgloss.Style
90+ Muted lipgloss.Style
8491}
8592
8693// DefaultStyles returns default styles.
8794func DefaultStyles () Styles {
8895 return Styles {
89- Title : lipgloss .NewStyle ().Bold (true ),
90- Label : lipgloss .NewStyle ().Faint (true ),
91- Value : lipgloss .NewStyle (),
92- JSON : lipgloss .NewStyle (),
93- Border : lipgloss .NewStyle (),
94- PanelTitle : lipgloss .NewStyle ().Bold (true ),
95- FocusBorder : lipgloss .NewStyle (),
96- Muted : lipgloss .NewStyle ().Faint (true ),
96+ Title : lipgloss .NewStyle ().Bold (true ),
97+ Label : lipgloss .NewStyle ().Faint (true ),
98+ Value : lipgloss .NewStyle (),
99+ JSON : lipgloss .NewStyle (),
100+ JSONKey : lipgloss .NewStyle (),
101+ JSONString : lipgloss .NewStyle (),
102+ JSONNumber : lipgloss .NewStyle (),
103+ JSONBool : lipgloss .NewStyle (),
104+ JSONNull : lipgloss .NewStyle (),
105+ JSONPunctuation : lipgloss .NewStyle (),
106+ Border : lipgloss .NewStyle (),
107+ PanelTitle : lipgloss .NewStyle ().Bold (true ),
108+ FocusBorder : lipgloss .NewStyle (),
109+ Muted : lipgloss .NewStyle ().Faint (true ),
97110 }
98111}
99112
@@ -113,7 +126,7 @@ type Model struct {
113126 // Job data
114127 job * sidekiq.JobRecord
115128 properties []PropertyRow
116- jsonLines [] string
129+ jsonView jsonview. Model
117130
118131 // Scroll state
119132 leftYOffset int
@@ -124,10 +137,9 @@ type Model struct {
124137 focusRight bool
125138
126139 // Calculated dimensions
127- leftWidth int
128- rightWidth int
129- panelHeight int
130- maxJSONWidth int
140+ leftWidth int
141+ rightWidth int
142+ panelHeight int
131143}
132144
133145const (
@@ -141,8 +153,9 @@ type Option func(*Model)
141153// New creates a new job detail model.
142154func New (opts ... Option ) Model {
143155 m := Model {
144- KeyMap : DefaultKeyMap (),
145- styles : DefaultStyles (),
156+ KeyMap : DefaultKeyMap (),
157+ styles : DefaultStyles (),
158+ jsonView : jsonview .New (),
146159 }
147160
148161 for _ , opt := range opts {
@@ -156,6 +169,16 @@ func New(opts ...Option) Model {
156169func WithStyles (s Styles ) Option {
157170 return func (m * Model ) {
158171 m .styles = s
172+ m .jsonView .SetStyles (jsonview.Styles {
173+ Text : s .JSON ,
174+ Key : s .JSONKey ,
175+ String : s .JSONString ,
176+ Number : s .JSONNumber ,
177+ Bool : s .JSONBool ,
178+ Null : s .JSONNull ,
179+ Punctuation : s .JSONPunctuation ,
180+ Muted : s .Muted ,
181+ })
159182 }
160183}
161184
@@ -172,12 +195,23 @@ func WithSize(width, height int) Option {
172195 m .width = width
173196 m .height = height
174197 m .updateDimensions ()
198+ m .jsonView .SetSize (width , height )
175199 }
176200}
177201
178202// SetStyles sets the styles.
179203func (m * Model ) SetStyles (s Styles ) {
180204 m .styles = s
205+ m .jsonView .SetStyles (jsonview.Styles {
206+ Text : s .JSON ,
207+ Key : s .JSONKey ,
208+ String : s .JSONString ,
209+ Number : s .JSONNumber ,
210+ Bool : s .JSONBool ,
211+ Null : s .JSONNull ,
212+ Punctuation : s .JSONPunctuation ,
213+ Muted : s .Muted ,
214+ })
181215}
182216
183217// SetSize sets the dimensions.
@@ -186,6 +220,7 @@ func (m *Model) SetSize(width, height int) {
186220 m .height = height
187221 m .updateDimensions ()
188222 m .clampScroll ()
223+ m .jsonView .SetSize (width , height )
189224}
190225
191226// SetJob sets the job to display.
@@ -293,7 +328,7 @@ func (m Model) maxLeftYOffset() int {
293328}
294329
295330func (m Model ) maxRightYOffset () int {
296- maxY := len ( m . jsonLines ) - m .panelHeight
331+ maxY := m . jsonView . LineCount ( ) - m .panelHeight
297332 if maxY < 0 {
298333 return 0
299334 }
@@ -302,7 +337,7 @@ func (m Model) maxRightYOffset() int {
302337
303338func (m Model ) maxRightXOffset () int {
304339 contentWidth := max (m .rightWidth - 2 - 2 * jobDetailPanelPadding , 0 )
305- maxX := m .maxJSONWidth - contentWidth
340+ maxX := m .jsonView . MaxWidth () - contentWidth
306341 if maxX < 0 {
307342 return 0
308343 }
@@ -446,27 +481,11 @@ func (m *Model) extractProperties() {
446481
447482// formatJSON creates pretty-printed JSON lines.
448483func (m * Model ) formatJSON () {
449- m .jsonLines = nil
450- m .maxJSONWidth = 0
451-
452484 if m .job == nil {
485+ m .jsonView .SetValue (nil )
453486 return
454487 }
455-
456- b , err := json .MarshalIndent (m .job .Item (), "" , " " )
457- if err != nil {
458- m .jsonLines = []string {"{}" , " Error formatting JSON" }
459- return
460- }
461-
462- m .jsonLines = strings .Split (string (b ), "\n " )
463-
464- // Calculate max width for horizontal scroll
465- for _ , line := range m .jsonLines {
466- if len (line ) > m .maxJSONWidth {
467- m .maxJSONWidth = len (line )
468- }
469- }
488+ m .jsonView .SetValue (m .job .Item ())
470489}
471490
472491// renderLeftPanel renders the properties panel.
@@ -531,19 +550,15 @@ func (m Model) renderRightPanel() string {
531550 contentWidth := max (innerWidth - 2 * jobDetailPanelPadding , 0 )
532551
533552 // Content lines with horizontal scroll
534- endY := min (m .rightYOffset + m .panelHeight , len ( m . jsonLines ))
553+ endY := min (m .rightYOffset + m .panelHeight , m . jsonView . LineCount ( ))
535554 contentCap := 0
536555 if endY > m .rightYOffset {
537556 contentCap = endY - m .rightYOffset
538557 }
539558 contentLines := make ([]string , 0 , contentCap )
540559
541560 for i := m .rightYOffset ; i < endY ; i ++ {
542- line := m .jsonLines [i ]
543- // Apply horizontal scroll BEFORE styling
544- line = applyHorizontalScroll (line , m .rightXOffset , contentWidth )
545- line = m .styles .JSON .Render (line )
546- contentLines = append (contentLines , line )
561+ contentLines = append (contentLines , m .jsonView .RenderLine (i , m .rightXOffset , contentWidth ))
547562 }
548563
549564 // Pad to panel height
@@ -584,40 +599,23 @@ func formatTimestamp(ts float64) string {
584599 return t .Format ("2006-01-02 15:04:05" )
585600}
586601
587- // applyHorizontalScroll applies horizontal scroll offset.
588- func applyHorizontalScroll (line string , offset , visibleWidth int ) string {
589- runes := []rune (line )
590-
591- if offset >= len (runes ) {
592- return strings .Repeat (" " , visibleWidth )
593- }
594- runes = runes [offset :]
595-
596- if len (runes ) < visibleWidth {
597- return string (runes ) + strings .Repeat (" " , visibleWidth - len (runes ))
598- }
599- return string (runes [:visibleWidth ])
600- }
601-
602602// wrapText wraps text to fit within the specified width.
603603func wrapText (s string , width int ) []string {
604604 if width <= 0 {
605605 return []string {s }
606606 }
607607
608- runes := []rune (s )
609- if len (runes ) <= width {
608+ if lipgloss .Width (s ) <= width {
610609 return []string {s }
611610 }
612611
613612 var lines []string
614- for len (runes ) > 0 {
615- if len (runes ) <= width {
616- lines = append (lines , string (runes ))
617- break
618- }
619- lines = append (lines , string (runes [:width ]))
620- runes = runes [width :]
613+ for lipgloss .Width (s ) > width {
614+ lines = append (lines , ansi .Truncate (s , width , "" ))
615+ s = ansi .Cut (s , width , lipgloss .Width (s ))
616+ }
617+ if s != "" {
618+ lines = append (lines , s )
621619 }
622620 return lines
623621}
0 commit comments