Skip to content

Commit 1cfef86

Browse files
committed
feat: add formatted and raw display modes for results
Add two display modes for the results pane: - Raw mode (1): Shows compact JSON preview of log entries - Formatted mode (2): Shows structured view with timestamp and message highlighted Changes: - Introduce detailMode enum (detailModeRaw, detailModeFormatted) - Split result delegate into resultItemDelegateRaw and resultItemDelegateFormatted - Add resultsListRaw and resultsListFormatted to model state - Implement keyboard shortcuts '1' and '2' to switch modes - Apply lipgloss styling for message and timestamp in formatted mode - Update View() to render appropriate results list based on mode The formatted mode extracts 'message' and 'timestamp' fields from log entries and displays them with styling, while raw mode shows the full JSON data as before.
1 parent 8062b2e commit 1cfef86

File tree

1 file changed

+164
-65
lines changed

1 file changed

+164
-65
lines changed

tui.go

Lines changed: 164 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ import (
1818
"github.com/charmbracelet/lipgloss"
1919
)
2020

21+
type detailMode int
22+
23+
const (
24+
detailModeRaw detailMode = iota
25+
detailModeFormatted
26+
)
27+
2128
// Custom styles for result items
2229
var (
2330
resultItemStyle = lipgloss.NewStyle().
@@ -35,33 +42,30 @@ var (
3542
PaddingRight(2).
3643
BorderStyle(lipgloss.RoundedBorder()).
3744
BorderForeground(lipgloss.Color("170"))
45+
46+
msgStyle = lipgloss.NewStyle().Bold(true)
47+
timestampStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("241"))
3848
)
3949

40-
// resultItemDelegate is a custom delegate for rendering result items in list view
41-
type resultItemDelegate struct{}
50+
type resultItemDelegateRaw struct{}
4251

43-
func (d resultItemDelegate) Height() int { return 2 }
44-
func (d resultItemDelegate) Spacing() int { return 1 }
45-
func (d resultItemDelegate) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
46-
func (d resultItemDelegate) Render(w io.Writer, m list.Model, index int, item list.Item) {
52+
func (d resultItemDelegateRaw) Height() int { return 2 }
53+
func (d resultItemDelegateRaw) Spacing() int { return 1 }
54+
func (d resultItemDelegateRaw) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
55+
func (d resultItemDelegateRaw) Render(w io.Writer, m list.Model, index int, item list.Item) {
4756
result, ok := item.(resultItem)
4857
if !ok {
4958
return
5059
}
5160

52-
// Show a compact preview across 2 lines
5361
data, _ := json.Marshal(result.data)
5462
preview := string(data)
5563

56-
// Calculate available width per line
57-
maxLen := m.Width() - 4 // Reduce the padding subtraction
58-
59-
isSelected := index == m.Index()
60-
61-
// Split into two lines
62-
line1 := fmt.Sprintf("%d. %s", index+1, preview)
64+
line1 := preview
6365
line2 := ""
6466

67+
maxLen := m.Width() - 4
68+
6569
if len(preview) > maxLen {
6670
line1 = preview[:maxLen]
6771
if len(preview) > maxLen*2 {
@@ -71,22 +75,73 @@ func (d resultItemDelegate) Render(w io.Writer, m list.Model, index int, item li
7175
}
7276
}
7377

74-
firstLine := line1
75-
secondLine := fmt.Sprintf("%s", line2)
76-
77-
output := firstLine
78+
output := line1
7879
if line2 != "" {
79-
output += "\n" + secondLine
80+
output += "\n" + line2
8081
}
8182

83+
isSelected := index == m.Index()
84+
8285
if isSelected {
8386
fmt.Fprint(w, selectedResultStyle.Render(output))
8487
} else {
8588
fmt.Fprint(w, resultItemStyle.Render(output))
8689
}
8790
}
8891

89-
// resultItemDelegate is a custom delegate for rendering result items in list view
92+
type resultItemDelegateFormatted struct{}
93+
94+
func (d resultItemDelegateFormatted) Height() int { return 2 }
95+
func (d resultItemDelegateFormatted) Spacing() int { return 1 }
96+
func (d resultItemDelegateFormatted) Update(msg tea.Msg, m *list.Model) tea.Cmd { return nil }
97+
98+
func (d resultItemDelegateFormatted) Render(w io.Writer, m list.Model, index int, item list.Item) {
99+
result, ok := item.(resultItem)
100+
if !ok {
101+
return
102+
}
103+
104+
data, _ := json.Marshal(result.data)
105+
preview := string(data)
106+
107+
line1 := ""
108+
109+
if msg, ok := result.data["message"]; ok {
110+
if message, ok := msg.(string); ok {
111+
line1 = msgStyle.Render(message)
112+
} else {
113+
line1 = msgStyle.Render(fmt.Sprintf("%v", msg))
114+
}
115+
}
116+
117+
if ts, ok := result.data["timestamp"]; ok {
118+
if timestamp, ok := ts.(string); ok {
119+
line1 = fmt.Sprintf("%s - %s", timestampStyle.Render(timestamp), line1)
120+
}
121+
}
122+
123+
maxLen := m.Width() - 4
124+
125+
line2 := preview
126+
127+
if len(line1) > maxLen {
128+
line1 = line1[:maxLen]
129+
}
130+
if len(preview) > maxLen {
131+
line2 = preview[:maxLen]
132+
} else {
133+
line2 = preview
134+
}
135+
136+
output := line1 + "\n" + line2
137+
isSelected := index == m.Index()
138+
139+
if isSelected {
140+
fmt.Fprint(w, selectedResultStyle.Render(output))
141+
} else {
142+
fmt.Fprint(w, resultItemStyle.Render(output))
143+
}
144+
}
90145

91146
type pane int
92147

@@ -146,13 +201,14 @@ type model struct {
146201
size int
147202
maxPages int64
148203

149-
queryInput textinput.Model
150-
fieldsList list.Model
151-
valuesList list.Model
152-
resultsList list.Model
153-
detailView viewport.Model
154-
spinner spinner.Model
155-
debugView string
204+
queryInput textinput.Model
205+
fieldsList list.Model
206+
valuesList list.Model
207+
resultsListRaw list.Model
208+
resultsListFormatted list.Model
209+
detailView viewport.Model
210+
spinner spinner.Model
211+
debugView string
156212

157213
selectedField fieldItem
158214

@@ -171,6 +227,8 @@ type model struct {
171227
fieldValues map[string]map[string]int
172228
showingDetail bool
173229

230+
detailMode detailMode
231+
174232
err error
175233
loading bool
176234
}
@@ -220,40 +278,52 @@ func initialModel(ctx context.Context, config Config, query string) model {
220278
valuesList.SetFilteringEnabled(true)
221279

222280
// Results list showing compact previews
223-
resultsList := list.New([]list.Item{}, resultItemDelegate{}, 80, 20)
224-
resultsList.Title = "Results"
225-
resultsList.SetShowStatusBar(false)
226-
resultsList.SetFilteringEnabled(false)
227-
resultsList.SetShowHelp(false)
228-
resultsList.SetShowPagination(true)
229-
resultsList.SetShowTitle(true)
230-
resultsList.DisableQuitKeybindings()
231-
resultsList.SetFilteringEnabled(true)
281+
resultsListRaw := list.New([]list.Item{}, resultItemDelegateRaw{}, 80, 20)
282+
resultsListRaw.Title = "Results"
283+
resultsListRaw.SetShowStatusBar(false)
284+
resultsListRaw.SetFilteringEnabled(false)
285+
resultsListRaw.SetShowHelp(false)
286+
resultsListRaw.SetShowPagination(true)
287+
resultsListRaw.SetShowTitle(true)
288+
resultsListRaw.DisableQuitKeybindings()
289+
resultsListRaw.SetFilteringEnabled(true)
290+
291+
// Results list showing compact previews
292+
resultsListFormatted := list.New([]list.Item{}, resultItemDelegateFormatted{}, 80, 20)
293+
resultsListFormatted.Title = "Results"
294+
resultsListFormatted.SetShowStatusBar(false)
295+
resultsListFormatted.SetFilteringEnabled(false)
296+
resultsListFormatted.SetShowHelp(false)
297+
resultsListFormatted.SetShowPagination(true)
298+
resultsListFormatted.SetShowTitle(true)
299+
resultsListFormatted.DisableQuitKeybindings()
300+
resultsListFormatted.SetFilteringEnabled(true)
232301

233302
// Detail viewport for full JSON view
234303
detailView := viewport.New(0, 0)
235304

236305
return model{
237-
ctx: ctx,
238-
account: config.Account,
239-
token: config.Token,
240-
size: config.Size,
241-
maxPages: config.MaxPages,
242-
from: config.From,
243-
to: config.To,
244-
concurrency: config.Concurrency,
245-
queryInput: ti,
246-
fieldsList: fieldsList,
247-
valuesList: valuesList,
248-
resultsList: resultsList,
249-
detailView: detailView,
250-
spinner: spinner.New(),
251-
debugView: "",
252-
currentPane: queryPane,
253-
allFields: make(map[string]int),
254-
fieldValues: make(map[string]map[string]int),
255-
fieldPath: []string{},
256-
showingDetail: false,
306+
ctx: ctx,
307+
account: config.Account,
308+
token: config.Token,
309+
size: config.Size,
310+
maxPages: config.MaxPages,
311+
from: config.From,
312+
to: config.To,
313+
concurrency: config.Concurrency,
314+
queryInput: ti,
315+
fieldsList: fieldsList,
316+
valuesList: valuesList,
317+
resultsListRaw: resultsListRaw,
318+
resultsListFormatted: resultsListFormatted,
319+
detailView: detailView,
320+
spinner: spinner.New(),
321+
debugView: "",
322+
currentPane: queryPane,
323+
allFields: make(map[string]int),
324+
fieldValues: make(map[string]map[string]int),
325+
fieldPath: []string{},
326+
showingDetail: false,
257327
}
258328
}
259329

@@ -273,6 +343,17 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
273343

274344
case tea.KeyMsg:
275345
switch msg.String() {
346+
case "1":
347+
if m.currentPane == resultsPane && !m.showingDetail {
348+
m.detailMode = detailModeRaw
349+
}
350+
return m, nil
351+
case "2":
352+
if m.currentPane == resultsPane && !m.showingDetail {
353+
m.detailMode = detailModeFormatted
354+
}
355+
return m, nil
356+
276357
case "ctrl+c", "q":
277358
return m, tea.Quit
278359

@@ -320,10 +401,19 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
320401

321402
if m.currentPane == resultsPane {
322403
// Show detail view for selected result
323-
if item, ok := m.resultsList.SelectedItem().(resultItem); ok {
324-
m.showDetailView(item)
325-
m.showingDetail = true
326-
m.currentPane = detailPane
404+
if m.detailMode == detailModeRaw {
405+
if item, ok := m.resultsListRaw.SelectedItem().(resultItem); ok {
406+
m.showDetailView(item)
407+
m.showingDetail = true
408+
m.currentPane = detailPane
409+
}
410+
} else {
411+
// Formatted mode
412+
if item, ok := m.resultsListFormatted.SelectedItem().(resultItem); ok {
413+
m.showDetailView(item)
414+
m.showingDetail = true
415+
m.currentPane = detailPane
416+
}
327417
}
328418
return m, nil
329419
}
@@ -384,7 +474,8 @@ func (m model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
384474
cmds = append(cmds, cmd)
385475
case resultsPane:
386476
var cmd tea.Cmd
387-
m.resultsList, cmd = m.resultsList.Update(msg)
477+
m.resultsListRaw, cmd = m.resultsListRaw.Update(msg)
478+
m.resultsListFormatted, _ = m.resultsListFormatted.Update(msg)
388479
cmds = append(cmds, cmd)
389480
}
390481
}
@@ -410,7 +501,8 @@ func (m *model) updateSizes() {
410501
// Set sizes to content area (borders will be added by lipgloss)
411502
m.fieldsList.SetSize(leftPaneWidth, paneHeight-2)
412503
m.valuesList.SetSize(midPaneWidth, paneHeight-2)
413-
m.resultsList.SetSize(rightPaneWidth, paneHeight-2)
504+
m.resultsListRaw.SetSize(rightPaneWidth, paneHeight-2)
505+
m.resultsListFormatted.SetSize(rightPaneWidth, paneHeight-2)
414506

415507
// Store widths and height for rendering
416508
m.fieldsWidth = leftPaneWidth
@@ -487,7 +579,12 @@ func (m model) View() string {
487579

488580
fieldsSection := fieldsStyle.Width(m.fieldsWidth).MaxHeight(m.paneHeight).Render(m.fieldsList.View())
489581
valuesSection := valuesStyle.Width(m.valuesWidth).MaxHeight(m.paneHeight).Render(m.valuesList.View())
490-
resultsSection := resultsStyle.Width(m.resultsWidth).MaxHeight(m.paneHeight).Render(m.resultsList.View())
582+
var resultsSection string
583+
if m.detailMode == detailModeRaw {
584+
resultsSection = resultsStyle.Width(m.resultsWidth).MaxHeight(m.paneHeight).Render(m.resultsListRaw.View())
585+
} else {
586+
resultsSection = resultsStyle.Width(m.resultsWidth).MaxHeight(m.paneHeight).Render(m.resultsListFormatted.View())
587+
}
491588

492589
panesRow := lipgloss.JoinHorizontal(lipgloss.Top,
493590
fieldsSection,
@@ -677,14 +774,16 @@ func (m *model) updateResultsView() {
677774
var items []list.Item
678775

679776
for i, result := range m.results {
680-
m.resultsList.SetItems(items)
777+
m.resultsListRaw.SetItems(items)
778+
m.resultsListFormatted.SetItems(items)
681779
items = append(items, resultItem{
682780
index: i,
683781
data: result,
684782
})
685783
}
686784

687-
m.resultsList.SetItems(items)
785+
m.resultsListRaw.SetItems(items)
786+
m.resultsListFormatted.SetItems(items)
688787
}
689788

690789
func replaceExisitingSearch(query, field, value string) string {

0 commit comments

Comments
 (0)